为什么 rust 需要可变引用的显式生命周期而不是常规引用?

问题描述 投票:0回答:1

这是一个简单的示例,您只需指定可变引用的生命周期:

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 borrow-checker
1个回答
0
投票

首先,我们来谈谈终身省略。所有引用都有一些与之相关的生命周期,但 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]

过度约束,您稍后会在使用 bar_mut

时遇到问题。例如,您无法对同一值调用它两次。请参阅
操场

上的失败。请参阅

为什么这个可变借用超出了其范围?
这在不同的场景中解释了更多。

© www.soinside.com 2019 - 2024. All rights reserved.