这是一个简单的示例,您只需指定可变引用的生命周期:
fn foo<'a>(x: &'a [&'a i32]) -> impl Iterator<Item = ()> + 'a {
x.iter().map(|_| ())
}
fn foo_mut<'a>(x: &'a mut [&'a i32]) -> impl Iterator<Item = ()> + 'a {
x.iter().map(|_| ())
}
fn bar(x: &[&i32]) {
for _ in foo(x) {}
}
// why is an explicit lifetime needed?
fn bar_mut<'a>(x: &'a mut [&'a i32]) {
for _ in foo_mut(x) {}
}
我不想指定这些生命周期,因为这会使调用该函数变得更加困难。
这只是一些编译器错误还是我遗漏了什么?
您可以通过使用宏来解决此问题,但我真的希望能够将其定义为函数而不指定生命周期。
首先,我们来谈谈终身省略。所有引用都有一些与之相关的生命周期,但 Rust 允许您在可以适当推断时省略注释。
如果你要写:
fn bar_mut(x: &mut [&i32]) {
for _ in foo_mut(x) {}
}
编译器会推断每个引用都有自己的生命周期:
fn bar_mut<'a, 'b>(x: &'a mut [&'b i32]) {
for _ in foo_mut(x) {}
}
如您所见,
bar_mut
的签名与foo_mut
的签名不同;切片的生命周期,&[_]' doesn't have to match the lifetime of its elements, &i32', so it can't satisfy the constraints set by
foo_mut`。
那么为什么它适用于
foo
和 bar
?
其次,我们来谈谈方差。特别是,生命周期可以由编译器(根据其差异)调整为所需的最小框架。通常,引用的生命周期称为“协变”,这意味着编译器可以根据需要缩短它们。 如果我们看具有显式生命周期的
bar
:
fn bar<'a, 'b>(x: &'a [&'b i32]) {
for _ in foo(x) {}
}
当将 'b
传递给 'a
时,
x
的生命周期会缩短以匹配
foo
,并且它可以做到这一点,因为
'b
是协变的。由于嵌套引用的构造,还存在隐含的
'b: 'a
约束,这使得它保证有效,但目前并不重要。
但是,可变引用有点严格,因为它们的内容可以重新分配。可变引用的生命周期仍然是协变的,但它的“引用”类型必须是“不变”。否则,您将能够通过该引用分配一个比实际需要的寿命短的值。因此,如果我们查看您的签名
foo_mut
:
fn foo_mut<'a>(x: &'a mut [&'a i32]) -> ...
与切片关联的生命周期,&mut [_]
,可以是协变的,但元素的生命周期,
&i32
,必须是不变的,因为它位于可变引用后面。因此
'a
是不变的。因此,当我们查看具有不同生命周期的
bar_mut
时:
fn bar_mut<'a, 'b>(x: &'a mut [&'b i32]) {
for _ in foo_mut(x) {}
}
生命周期
'b
同样是不变的,因此在传递到
foo_mut
时它保持不变。由于 foo_mut
要求生命周期相同,因此 'a
必须与
'b
匹配。并且
'b
不能被缩短,所以
'a
必须是extend
但这是不允许的,因为
'a
只是covariant
(即它只能被缩短)。这就是你得到错误的方式:
error: lifetime may not live long enough
--> src/lib.rs:15:14
|
14 | fn bar_mut<'a, 'b>(x: &'a mut [&'b i32]) {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
15 | for _ in foo_mut(x) {}
| ^^^^^^^^^^ argument requires that `'a` must outlive `'b`
我真的希望能够将其定义为一个函数而不指定生命周期。
你陷入困境只是因为
foo
foo_mut
不必要地过度受限。如果您仅将生命周期注释为需要的内容,则根本不会遇到此问题:
fn foo<'a>(x: &'a [&i32]) -> impl Iterator<Item = ()> + 'a {
x.iter().map(|_| ())
}
fn foo_mut<'a>(x: &'a mut [&i32]) -> impl Iterator<Item = ()> + 'a {
x.iter().map(|_| ())
}
fn bar(x: &[&i32]) {
for _ in foo(x) {}
}
fn bar_mut(x: &mut [&i32]) {
for _ in foo_mut(x) {}
}
如图所示,您只需要外部生命周期,因为这就是您的迭代器类型所依赖的。从技术上讲,它也依赖于
&i32
的生命周期,但由于嵌套引用的生命周期本质上受到限制,因此您可以免费获得它。您仍然需要
'a
,因为您需要告诉编译器您的 impl Iterator
可以引用什么。
关键是您应该尽可能使用不同的生命周期,并在不必要时省略。playground
上的完整代码。最后一点:因为
&'a mut [&'a i32]
时遇到问题。例如,您无法对同一值调用它两次。请参阅上的失败。请参阅
为什么这个可变借用超出了其范围?这在不同的场景中解释了更多。