警告:我说的是我并不真正具备的专业水平。鉴于这篇文章的长度,我可能错了很多次。
TL;DR:顶级值的生命周期是协变的。引用值的生命周期是不变的。
介绍问题
您可以通过将VecRef<'a> 替换为&'a mut T 来显着简化示例。
此外,应该删除main,因为谈论函数的一般行为比某些特定的生命周期实例化更完整。
我们用这个函数代替VecRefRef的构造函数:
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
在我们进一步讨论之前,了解生命周期是如何在 Rust 中隐式转换的很重要。当一个人将一个指针分配给另一个显式注释的名称时,就会发生生命周期强制。这允许的最明显的事情是缩短顶级指针的生命周期。因此,这不是典型的举动。
旁白:我说“显式注释”是因为in implicit cases like let x = y or fn f<T>(_: T) {}, reborrowing doesn't seem to happen。目前尚不清楚这是否是有意的。
完整的例子是
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
同样的错误:
error[E0623]: lifetime mismatch
--> src/main.rs:5:26
|
4 | fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
| ------------------
| |
| these two types are declared with different lifetimes...
5 | use_same_ref_ref(reference);
| ^^^^^^^^^ ...but data from `reference` flows into `reference` here
一个简单的修复
可以通过以下方式修复它
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a>(reference: &'a mut &'a mut ()) {
use_same_ref_ref(reference);
}
因为签名现在在逻辑上是相同的。但是,不明显的是为什么
let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;
use_ref_ref(ref_ref);
能够生成&'a mut &'a mut ()。
一个不那么简单的修复
可以改为强制执行'a: 'b
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
这意味着外部引用的生命周期至少与内部引用的生命周期一样大。
不明显
我希望回答这些问题。
非修复
断言'b: 'a 并不能解决问题。
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a, 'b: 'a>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
另一个更令人惊讶的修复
使外部引用不可变可以解决问题
fn use_same_ref_ref<'c>(reference: &'c &'c mut ()) {}
fn use_ref_ref<'a, 'b>(reference: &'a &'b mut ()) {
use_same_ref_ref(reference);
}
还有一个更令人惊讶的未修复!
使 inner 引用不可变根本没有帮助!
fn use_same_ref_ref<'c>(reference: &'c mut &'c ()) {}
fn use_ref_ref<'a, 'b>(reference: &'a mut &'b ()) {
use_same_ref_ref(reference);
}
但是为什么??!
原因是……
等一下,首先我们要讨论方差
计算机科学中两个非常重要的概念是协变和逆变。我不会使用这些名称(我会非常明确地说明我的投射方式),但这些名称对于searching the internet 仍然非常有用。
在理解这里的行为之前,理解方差的概念非常重要。如果您参加过涵盖此内容的大学课程,或者您可以从其他上下文中记住它,那么您处于一个很好的位置。不过,您可能仍然会感谢将想法与生命周期联系起来的帮助。
简单的例子——一个普通的指针
考虑一些带有指针的堆栈位置:
║ Name │ Type │ Value
───╫───────────┼─────────────────────┼───────
1 ║ val │ i32 │ -1
───╫───────────┼─────────────────────┼───────
2 ║ reference │ &'x mut i32 │ 0x1
堆栈向下增长,所以reference堆栈位置是在val之后创建的,并且会在val之前被移除。
考虑一下
let new_ref = reference;
得到
║ Name │ Type │ Value
───╫───────────┼─────────────┼───────
1 ║ val │ i32 │ -1
───╫───────────┼─────────────┼───────
2 ║ reference │ &'x mut i32 │ 0x1
───╫───────────┼─────────────┼───────
3 ║ new_ref │ &'y mut i32 │ 0x1
'y 的有效生命周期是多少?
考虑两个可变指针操作:
Read 阻止'y 增长,因为'x 引用仅保证对象在'x 范围内保持活动状态。但是,read 不会阻止 'y 缩小,因为当指向的值处于活动状态时,任何读取都会产生一个独立于生命周期 'y 的值。
Write 也防止'y 增长,因为无法写入无效指针。但是,write 并不能阻止 'y 收缩,因为对指针的任何写入都会复制其中的值,这使其与 'y 的生命周期无关。
hard case - 指针指针
用指针指针考虑一些堆栈位置:
║ Name │ Type │ Value
───╫───────────┼─────────────────────┼───────
1 ║ val │ i32 │ -1
───╫───────────┼─────────────────────┼───────
2 ║ reference │ &'a mut i32 │ 0x1
───╫───────────┼─────────────────────┼───────
3 ║ ref_ref │ &'x mut &'a mut i32 │ 0x2
考虑一下
let new_ref_ref = ref_ref;
得到
║ Name │ Type │ Value
───╫─────────────┼─────────────────────┼───────
1 ║ val │ i32 │ -1
───╫─────────────┼─────────────────────┼───────
2 ║ reference │ &'a mut i32 │ 0x1
───╫─────────────┼─────────────────────┼───────
3 ║ ref_ref │ &'x mut &'a mut i32 │ 0x2
───╫─────────────┼─────────────────────┼───────
4 ║ new_ref_ref │ &'y mut &'b mut i32 │ 0x2
现在有两个问题:
'y 的有效生命周期是多少?
'b 的有效生命周期有哪些?
让我们首先考虑'y 与两个可变指针操作:
Read 阻止'y 增长,因为'x 引用仅保证对象在'x 范围内保持活动状态。但是,read 不会阻止 'y 缩小,因为当指向的值处于活动状态时,任何读取都将产生一个独立于生命周期 'y 的值。
Write 也防止'y 增长,因为无法写入无效指针。但是,write 不会阻止 'y 收缩,因为对指针的任何写入都会复制其中的值,这使其与 'y 的生命周期无关。
这和以前一样。
现在,考虑 'b 和两个可变指针操作
Read 防止'b 增长,因为如果要从外部指针中提取内部指针,您将能够在'a 过期后读取它。
Write 也防止'b 增长,因为如果要从外部指针中提取内部指针,您将能够在'a 过期后对其进行写入。
Read 和 write 一起还可以防止 'b 缩小,因为这种情况:
let ref_ref: &'x mut &'a mut i32 = ...;
{
// Has lifetime 'b, which is smaller than 'a
let new_val: i32 = 123;
// Shrink 'a to 'b
let new_ref_ref: &'x mut &'b mut i32 = ref_ref;
*new_ref_ref = &mut new_val;
}
// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a mut i32 = *ref_ref;
// Oops, we have an &'a mut i32 pointer to a dropped value!
所以,'b 不能收缩,也不能从 'a 增长,所以 'a == 'b 完全一样。这意味着 &'y mut &'b mut i32 在生命周期 'b 中是不变的。
好的,这能解决我们的问题吗?
还记得代码吗?
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
当您调用 use_same_ref_ref 时,会尝试进行强制转换
&'a mut &'b mut () → &'c mut &'c mut ()
现在请注意'b == 'c,因为我们讨论了方差。因此我们实际上是在铸造
&'a mut &'b mut () → &'b mut &'b mut ()
外层&'a只能缩小。为了做到这一点,编译器需要知道
'a: 'b
编译器不知道这一点,因此编译失败。
我们的其他例子呢?
第一个是
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a>(reference: &'a mut &'a mut ()) {
use_same_ref_ref(reference);
}
编译器现在需要'a: 'a,而不是'a: 'b,这是非常正确的。
第二个直接断言'a: 'b
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
第三个断言'b: 'a
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a, 'b: 'a>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
这不起作用,因为这不是所需的断言。
不变性呢?
我们这里有两个案例。第一个是使外部引用不可变。
fn use_same_ref_ref<'c>(reference: &'c &'c mut ()) {}
fn use_ref_ref<'a, 'b>(reference: &'a &'b mut ()) {
use_same_ref_ref(reference);
}
这个成功了。为什么?
好吧,考虑一下我们从以前缩小&'b 的问题:
Read 和 write 一起还可以防止 'b 缩小,因为这种情况:
let ref_ref: &'x mut &'a mut i32 = ...;
{
// Has lifetime 'b, which is smaller than 'a
let new_val: i32 = 123;
// Shrink 'a to 'b
let new_ref_ref: &'x mut &'b mut i32 = ref_ref;
*new_ref_ref = &mut new_val;
}
// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a mut i32 = *ref_ref;
// Oops, we have an &'a mut i32 pointer to a dropped value!
所以,'b 不能缩小,也不能从 'a 增长,所以 'a == 'b 完全一样。
这只会发生,因为我们可以将内部引用换成一些新的、寿命不够长的引用。如果我们不能交换参考,这不是问题。因此缩短内部引用的生命周期是可能的。
那失败的呢?
使内部引用不可变并没有帮助:
fn use_same_ref_ref<'c>(reference: &'c mut &'c ()) {}
fn use_ref_ref<'a, 'b>(reference: &'a mut &'b ()) {
use_same_ref_ref(reference);
}
当您考虑到前面提到的问题从不涉及从内部引用中读取时,这是有道理的。事实上,这里修改了有问题的代码来证明:
let ref_ref: &'x mut &'a i32 = ...;
{
// Has lifetime 'b, which is smaller than 'a
let new_val: i32 = 123;
// Shrink 'a to 'b
let new_ref_ref: &'x mut &'b i32 = ref_ref;
*new_ref_ref = &new_val;
}
// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a i32 = *ref_ref;
// Oops, we have an &'a i32 pointer to a dropped value!
还有一个问题
已经很久了,但是回想一下:
可以改为强制执行'a: 'b
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
这意味着外部引用的生命周期至少与内部引用的生命周期一样大。
不明显
我希望回答这些问题。
我们已经回答了第一个重点问题,但第二个问题呢? 'a: 'b 是否允许超过 'a == 'b?
考虑一些类型为&'x mut &'y mut () 的调用者。如果是'x : 'y,那么它将自动转换为&'y mut &'y mut ()。相反,如果'x == 'y,那么'x : 'y 已经成立!因此,只有当您希望将包含'x 的类型返回给调用者时,差异才重要,调用者是唯一能够区分两者的人。由于这里不是这样,所以两者是等价的。
还有一件事
如果你写
let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;
use_ref_ref(ref_ref);
use_ref_ref 的定义位置
fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
use_same_ref_ref(reference);
}
代码如何执行'a: 'b?经检查,事实恰恰相反!
记住了
let reference = &mut val;
能够缩短其生命周期,因为此时它是外部生命周期。因此,它可以引用比val 的实际生命周期更小的生命周期,即使指针超出该生命周期!