生命周期子类型化和 impl-trait
Lifetime sub-typing and impl-trait
我遇到了一种有趣的生命周期子类型化形式,我认为它是有效的,但编译器对此持怀疑态度。
考虑以下函数,它计算两个引用序列的点积。
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
的类型是实现 [= 的东西的子类型18=],其中 'b
是本地范围。
它的一些变体确实有效,例如 changing the impl traits to slices and using two lifetime paramters,但我很好奇有一种方法可以让 Rust 编译器在不更改签名的情况下接受像 dot_prod_wrap
这样的 wapper dot_prod
.
我也知道子类型和 impl trait
都很复杂,所以我上面提出的有效性论点可能是错误的。 “ys
的类型是实现 IntoIterator<Item = &'b usize>
的东西的子类型”这一说法尤其值得怀疑。
。一旦借用检查器推导出 impl IntoIterator<Item = &'a usize>
正确的生命周期 'a
就无法更改,即使是更短的生命周期。
原因是因为trait对象是黑盒子;他们可以在限制范围内做任何想做的事。这包括 iterior mutability 之类的东西,它不能使用更短的生命周期(否则你可以将生命周期更短的值分配给期望更长生命周期的对象)。它适用于切片,因为借用检查器知道生命周期是协变的;因此可以在需要时缩短它。
你应该dot_prod
使用两个独立的生命周期。
but I'm curious about there is a way of getting the Rust compiler to accept a wapper like dot_prod_wrap
without changing the signature of dot_prod
.
关于 Rust 这样做的原因是正确的:只知道 ys
的 IntoIterator::IntoIter
类型有一定的生命周期不足以让它知道它是否可以缩短寿命。 但是,Iterator<Item=&'a usize>
对我们程序员来说并不是一个不透明的黑盒子:
let ys = ys.into_iter().map(|y| y);
通过将迭代器中的所有值映射到它们自身,我们允许 Rust 为闭包选择合适的 return 类型。经过一番思考,rustc
确定为了进行 dot_prod
调用类型检查,需要缩短 returned 借用的生命周期以匹配 xs
。
.map(|y| y)
不会 做 类型系统之外的任何事情,因此 rustc
将在发布版本中完全优化它。 (它可以在所有情况下都这样做,因为这个闭包是 zero-sized 不透明类型。我不完全确定它 在所有情况下都如此 ,但我想不出来它不会这样做的原因。)
我遇到了一种有趣的生命周期子类型化形式,我认为它是有效的,但编译器对此持怀疑态度。
考虑以下函数,它计算两个引用序列的点积。
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
的类型是实现 [= 的东西的子类型18=],其中 'b
是本地范围。
它的一些变体确实有效,例如 changing the impl traits to slices and using two lifetime paramters,但我很好奇有一种方法可以让 Rust 编译器在不更改签名的情况下接受像 dot_prod_wrap
这样的 wapper dot_prod
.
我也知道子类型和 impl trait
都很复杂,所以我上面提出的有效性论点可能是错误的。 “ys
的类型是实现 IntoIterator<Item = &'b usize>
的东西的子类型”这一说法尤其值得怀疑。
impl IntoIterator<Item = &'a usize>
正确的生命周期 'a
就无法更改,即使是更短的生命周期。
原因是因为trait对象是黑盒子;他们可以在限制范围内做任何想做的事。这包括 iterior mutability 之类的东西,它不能使用更短的生命周期(否则你可以将生命周期更短的值分配给期望更长生命周期的对象)。它适用于切片,因为借用检查器知道生命周期是协变的;因此可以在需要时缩短它。
你应该dot_prod
使用两个独立的生命周期。
but I'm curious about there is a way of getting the Rust compiler to accept a wapper like
dot_prod_wrap
without changing the signature ofdot_prod
.
ys
的 IntoIterator::IntoIter
类型有一定的生命周期不足以让它知道它是否可以缩短寿命。 但是,Iterator<Item=&'a usize>
对我们程序员来说并不是一个不透明的黑盒子:
let ys = ys.into_iter().map(|y| y);
通过将迭代器中的所有值映射到它们自身,我们允许 Rust 为闭包选择合适的 return 类型。经过一番思考,rustc
确定为了进行 dot_prod
调用类型检查,需要缩短 returned 借用的生命周期以匹配 xs
。
.map(|y| y)
不会 做 类型系统之外的任何事情,因此 rustc
将在发布版本中完全优化它。 (它可以在所有情况下都这样做,因为这个闭包是 zero-sized 不透明类型。我不完全确定它 在所有情况下都如此 ,但我想不出来它不会这样做的原因。)