问题描述
我遇到了一种有趣的生命周期子类型形式,我认为这是有效的,但编译器对此持怀疑态度。
fn dot_prod<'a>(xs: impl IntoIterator<Item = &'a usize>,ys: impl IntoIterator<Item = &'a usize>) -> usize {
let mut acc = 0;
for (x,y) in xs.into_iter().zip(ys.into_iter()) {
acc += *x * *y;
}
acc
}
签名赋予两个序列中的引用相同的生命周期。 “两个输入的单一生命周期”模式很常见,因为子类型允许函数用于不同生命周期的引用。但是,这里的某些东西(可能是 impl trait
?)阻止了这一点。让我们看一个被阻止使用的例子(playground link):
fn dot_prod_wrap<'a>(xs: impl IntoIterator<Item = &'a usize>,ys: impl IntoIterator<Item = &'a usize>) -> usize {
let xs: Vec<usize> = xs.into_iter().cloned().collect();
let ys = ys.into_iter();
dot_prod(&xs,ys)
}
Rustc 拒绝了这一点,观察到本地 xs
对 'a
无效,从中我们可以推断它已经为函数调用的生命周期参数插入了 'a
。但是,我认为这应该通过插入本地范围的生命周期(称为 'b
)并推断 ys
具有一个类型,该类型是实现 {{ 1}},其中 IntoIterator<Item = &'b usize>
是本地范围。
此方法的一些变体确实有效,例如 changing the impl traits to slices 和 using two lifetime paramters,但我很好奇有一种方法可以让 Rust 编译器接受像 'b
这样的 wapper不改变 dot_prod_wrap
的签名。
我也知道子类型和 dot_prod
都很复杂,所以我上面提出的有效性论证可能是错误的。声明“impl trait
的类型是实现 ys
的东西的子类型”尤其值得怀疑。
解决方法
Traits are invariant over their generic parameters。一旦借用检查器推断出 impl IntoIterator<Item = &'a usize>
一个合适的生命周期 'a
,它就无法更改,甚至不能更改为更短的生命周期。
原因是因为trait对象是黑盒子;他们可以在自己的限制范围内做任何想做的事情。这包括诸如迭代可变性之类的东西,它们不能使用较短的生命周期(否则,您可以将一个生命周期较短的值分配给期望更长生命周期的对象)。它适用于切片,因为借用检查器知道生命周期是协变的;因此可以在需要时缩短。
您应该让 dot_prod
使用两个独立的生命周期。