Warning:我是从我并不真正具备的专业水平上来说的。考虑到这篇文章的长度,我可能错了很多次。
TL;DR:顶级值的生命周期是协变的。引用值的生命周期是不变的。
引入问题
您可以通过替换显着简化您的示例VecRef<'a>
with &'a mut T
.
此外,应该删除main
,因为谈论更完整general函数的行为而不是某些特定的生命周期实例化。
代替VecRefRef
的构造函数,让我们使用这个函数:
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
在我们进一步讨论之前,了解生命周期如何在 Rust 中隐式转换非常重要。当一个指针分配给另一个显式注释的名称时,就会发生生命周期强制。这允许的最明显的事情是缩短顶级指针的生命周期。因此,这不是一个典型的举动。
Aside:我说“明确注释”是因为在隐式情况下,例如let x = y or fn f<T>(_: T) {},重借似乎没有发生 https://stackoverflow.com/questions/32154387/why-is-the-mutable-reference-not-moved-here/32158223#32158223。目前尚不清楚这是否是有意为之。
完整的例子是
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);
}
这意味着外部引用的生命周期是at least与内部生命周期一样大。
并不明显
我希望能够回答这些问题。
一个非修复
断言'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);
}
但为什么??!
原因是...
等等,首先我们讨论方差
计算机科学中两个非常重要的概念是协方差 and 逆变。我不会使用这些名称(我会非常明确地说明我的铸造方式),但这些名称对于搜索互联网 https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science).
在理解这里的行为之前,理解方差的概念非常重要。如果您学过涵盖此内容的大学课程,或者您可以从其他上下文中记住它,那么您就处于有利地位。不过,您可能仍然会感激将这个想法与生命周期联系起来的帮助。
简单的情况 - 一个普通的指针
考虑一些带有指针的堆栈位置:
║ Name │ Type │ Value
───╫───────────┼─────────────────────┼───────
1 ║ val │ i32 │ -1
───╫───────────┼─────────────────────┼───────
2 ║ reference │ &'x mut i32 │ 0x1
堆栈向下增长,因此reference
堆栈位置是在之后创建的val
,并且将在之前删除val
is.
考虑一下你这样做
let new_ref = reference;
to get
║ 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
.
硬壳——指针指针
考虑一些带有指针的堆栈位置:
║ 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;
to get
║ 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 and 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!
Ergo, 'b
不能缩小,也不能增长'a
, so '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: 'b
,编译器现在需要'a: 'a
,这确实是事实。
第二个直接断言'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 and 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!
Ergo, 'b
不能缩小,也不能增长'a
, so '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);
}
这意味着外部引用的生命周期是at least与内部生命周期一样大。
并不明显
我希望能够回答这些问题。
我们已经回答了第一个要点问题,那么第二个问题呢?做'a: 'b
允许超过'a == 'b
?
考虑一些具有类型的调用者&'x mut &'y mut ()
. If 'x : 'y
,那么它会自动转换为&'y mut &'y mut ()
。相反,如果'x == 'y
, then 'x : 'y
已经成立了!因此,只有当您希望返回包含以下内容的类型时,差异才重要:'x
对于呼叫者来说,他是唯一能够区分这两者的人。由于这里不是这种情况,因此两者是等效的。
还有一件事
如果你写
let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;
use_ref_ref(ref_ref);
where 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;
能够缩短其生命周期,因为此时它是外部生命周期。因此,它可以指一生smaller比实际寿命val
,即使指针超出了该生命周期!