警告:我所说的专业水平并不是很高。鉴于本文的长度,我可能会犯很多错误。
TL;DR:顶层值的生命周期是协变的。引用值的生命周期是不变的。
问题介绍
您可以通过将VecRef<'a>
替换为&'a mut T
来大大简化示例。
此外,应该删除main
,因为更完整地讨论函数的一般行为比某些特定的生命周期实例化更好。
我们可以使用这个函数来代替VecRefRef
的构造函数:
fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
在我们进一步探讨之前,了解 Rust 中生命周期如何隐式转换很重要。当将一个指针分配给另一个显式注释的名称时,会发生生命周期强制转换。最明显的是,这使得顶级指针的寿命缩短。因此,这不是一种典型的操作。
附言:我说“显式注释”,因为在隐式情况下,例如 let x = y
或 fn f<T>(_: T) {}
,重新借用似乎不会发生。这是否有意是不清楚的。
完整的例子如下:
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);
}
甚至更令人惊讶的非修复方法!
使内部引用不可变根本没有帮助!
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);
}
但是为什么?!
原因是...
等一下,首先我们来介绍方差
在计算机科学中,两个非常重要的概念是协变性和逆变性。我不会使用这些名称(我会非常明确地说明我将如何转换),但是这些名称对于在互联网上搜索仍然非常有用。
在您理解这里的行为之前,了解方差的概念非常重要。如果您已经学过覆盖此内容的大学课程,或者您可以从其他上下文中记得它,那么您处于一个很好的位置。不过,将该想法与生命周期相关联的帮助可能仍然很有用。
简单情况-普通指针
考虑一个带有指针的堆栈位置:
║ 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
'的有效生命周期是什么?
考虑两个可变指针操作:
读取 防止'y'
增长,因为'x'
引用只保证对象在'x'
的范围内存活。但是,读取不能防止'y'
缩小,因为在指向的值存活期间进行的任何读取都会得到一个独立于生命周期'y'
的值。
写入也防止'y'
增长,因为无法写入失效的指针。但是,写入不能防止'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;
获取
║ 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
现在有两个问题:
1.
'y
的有效生命周期是什么?
2.
'b
的有效生命周期是什么?
让我们先考虑使用两个可变指针操作的
'y
:
- 读取
- 写入
读取会阻止
'y
增长,因为
'x
引用仅保证对象在
'x
作用域内保持活动状态。但是,读取不会阻止
'y
收缩,因为在指向的值存在期间进行的任何读取都会导致与生命周期
'y
无关的值。
写入也会阻止
'y
增长,因为不能写入无效的指针。但是,写入不会阻止
'y
收缩,因为对指针的任何写入都会将其复制到值中,从而使其独立于生命周期
'y
。
这与以前相同。
现在考虑具有两个可变指针操作的'b
读取会防止'b
增长,因为如果从外部指针中提取内部指针,则可以在'a
过期后读取它。
写入也会防止'b
增长,因为如果从外部指针中提取内部指针,则可以在'a
过期后向其写入。
读取和写入一起也会防止'b
收缩,因为存在以下情况:
let ref_ref: &'x mut &'a mut i32 = ...;
{
let new_val: i32 = 123;
let new_ref_ref: &'x mut &'b mut i32 = ref_ref;
*new_ref_ref = &mut new_val;
}
let ref_ref: &'a mut i32 = *ref_ref;
因此,'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 and write together also prevent 'b
from shrinking, because of this scenario:
let ref_ref: &'x mut &'a mut i32 = ...;
{
let new_val: i32 = 123;
let new_ref_ref: &'x mut &'b mut i32 = ref_ref;
*new_ref_ref = &mut new_val;
}
let ref_ref: &'a mut i32 = *ref_ref;
Ergo, 'b
cannot shrink and it cannot grow from 'a
, so 'a == 'b
exactly.
这只有在我们能够将内部引用替换为一些新的、不够长寿的引用时才会发生。如果我们无法交换引用,这就不是一个问题。因此,缩短内部引用的寿命是可能的。
那失败的呢?
使内部引用不可变并不能解决问题:
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 = ...;
{
let new_val: i32 = 123;
let new_ref_ref: &'x mut &'b i32 = ref_ref;
*new_ref_ref = &new_val;
}
let ref_ref: &'a i32 = *ref_ref;
还有一个问题
已经过了相当长的时间,但是回想一下:
One can instead enforce '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);
}
This means that the lifetime of the outer reference is at least as large as the lifetime of the inner one.
It's not obvious
why &'a mut &'b mut ()
is not castable to &'c mut &'c mut ()
, or
whether this is better than &'a mut &'a mut ()
.
I hope to answer these questions.
我们已经回答了第一个问题,但第二个问题呢?
'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
的实际生命周期更小的生命周期!
'b
增长,因为如果从外部指针中提取内部指针,则在'a
过期后仍能读取它。-- 你能详细说明一下吗? - soupybionics&'static &'b
可以 转换为&'static &'static
,这表明你是对的,但我认为这不正确。相反,可能存在一个隐含的假设,即'b:'a
;将&'a &'b
转换为&'a &'static
会失败。 - Veedrac