为什么链接生命周期只与可变引用有关?

Why does linking lifetimes matter only with mutable references?

几天前, 有人遇到了一个可变引用的链接生命周期问题,该引用指向包含借用数据本身的类型。问题是提供对该类型的引用,该类型具有与类型内部借用数据相同生命周期的借用。 我试图重现问题:

struct VecRef<'a>(&'a Vec<u8>);

struct VecRefRef<'a>(&'a mut VecRef<'a>);

fn main() {
    let v = vec![8u8, 9, 10];
    let mut ref_v = VecRef(&v);
    create(&mut ref_v);
}

fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
    VecRefRef(r);
}

Example code

我在create()中明确注释了'b。这不编译:

error[E0623]: lifetime mismatch
  --> src/main.rs:12:15
   |
11 | fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
   |                      ------------------
   |                      |
   |                      these two types are declared with different lifetimes...
12 |     VecRefRef(r);
   |               ^ ...but data from `r` flows into `r` here

生命周期 'b 类似于 'b < 'a,因此违反了 VecRefRef<'a> 中的约束,即与 VecRef<'a> 的生命周期完全相同。

我将可变引用的生命周期与 VecRef<'a> 中的借用数据相关联:

fn create<'a>(r: &'a mut VecRef<'a>) {
    VecRefRef(r);
}

现在可以了。但为什么?我什至如何能够提供这样的参考? create() 中的可变引用 r 的生命周期为 VecRef<'a> 而不是 'a。为什么问题没有被推送到函数的调用端 create()?

我注意到另一件事我不明白。如果我在 VecRefRef<'a> 结构中使用 immutable 引用,那么在提供具有不同生命周期 'a:[=33 的引用时,它在某种程度上不再重要=]

struct VecRef<'a>(&'a Vec<u8>);

struct VecRefRef<'a>(&'a VecRef<'a>); // now an immutable reference

fn main() {
    let v = vec![8u8, 9, 10];
    let mut ref_v = VecRef(&v);
    create(&mut ref_v);
}

fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
    VecRefRef(r);
}

Example code

这与第一个示例相反,在第一个示例中,VecRefRef<'a>VecRef<'a> 进行了可变引用。我知道可变引用有不同的别名规则(根本没有别名),但这与这里的链接生命周期有什么关系?

The mutable reference r inside create() has the lifetime of VecRef<'a> not 'a

这是一个常见的混淆来源。检查此函数定义:

fn identity<'a, T>(val: &'a T) -> &'a T { val }

在函数定义中,'a 是一个 generic 生命周期参数,它与泛型类型参数 (T) 平行。当函数被调用时,调用者决定 'aT 的具体值是什么。让我们回顾一下你的 main:

fn main() {
    let v = vec![8u8, 9, 10];   // 1 |-lifetime of `v`
    let mut ref_v = VecRef(&v); // 2 |  |-lifetime of `ref_v` 
    create(&mut ref_v);         // 3 |  |
}

v 将在 main (1-3) 的整个 运行 中存活,但 ref_v 仅在最后两个语句 (2-3) 中存活.请注意,ref_v 指的是 一个比它长的值。如果您随后引用 ref_v,您将引用来自 (2-3) 的内容,而该内容本身也引用了来自 (1-3) 的内容。

查看你的固定方法:

fn create<'a>(r: &'a mut VecRef<'a>)

这表示对于这个函数调用,对VecRef的引用和它包含的引用必须相同。有一个生命周期可以选择满足这个——(2-3).

请注意,您的结构定义目前要求两个生命周期相同。您可以允许它们不同:

struct VecRefRef<'a, 'b: 'a>(&'a mut VecRef<'b>);
fn create<'a, 'b>(r: &'a mut VecRef<'b>)

请注意,您必须使用语法 'b: 'a 来表示生命周期 'b 将超过 'a

If I use an immutable reference [...], it somehow does not matter any more

这个我不太确定。我相信正在发生的事情是因为你有一个不可变的借用,编译器可以自动为你在较小的范围内重新借用。这允许生命周期匹配。正如您所指出的,可变引用不能有任何别名,即使是范围较小的别名,因此在这种情况下编译器也无能为力。

Warning: I'm speaking from a level of expertise that I don't really have. Given the length of this post, I'm probably wrong a large number of times.

TL;DR: Lifetimes of top-level values are covariant. Lifetimes of referenced values are invariant.

引入问题

您可以通过将 VecRef<'a> 替换为 &'a mut T 来显着简化示例。

此外,应该删除 main,因为谈论函数的 一般 行为比某些特定的生命周期实例化更完整。

代替VecRefRef的构造函数,让我们使用这个函数:

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

在我们继续之前,了解生命周期如何在 Rust 中隐式转换是很重要的。当将指针分配给另一个明确注释的名称时,就会发生生命周期强制。这允许的最明显的事情是缩短顶级指针的生命周期。因此,这不是典型的举动。

Aside: I say "explicitly annotated" because . It is not clear whether this is intended.

完整的例子是

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);
}

这意味着外部引用的生命周期至少与内部引用的生命周期一样大。

不明显

  • 为什么 &'a mut &'b mut () 不能转换为 &'c mut &'c mut (),或者

  • 这是否优于&'a mut &'a mut ().

希望能回答这些问题

未修复

声明 '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.

硬壳——指针指针

考虑一些带有指针指针的堆栈位置:

    ║ 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

  • 阅读
  • 写入

Read 阻止 'y 增长,因为 'x 引用仅保证对象在 'x 范围内保持活动状态。但是,read 不会阻止 'y 收缩,因为当指向的值处于活动状态时,任何读取都将产生一个独立于生命周期的值 'y

Write 也会阻止 'y 增长,因为不能写入无效指针。但是,write 不会阻止 'y 收缩,因为对指针的任何写入都会复制其中的值,这使其独立于生命周期 'y.

这个和以前一样

现在,考虑 'b 和两个可变指针操作

Read 阻止 'b 增长,因为如果要从外部指针中提取内部指针,您将能够在 [=78= 之后读取它] 已过期。

Write 也会阻止 'b 增长,因为如果要从外部指针中提取内部指针,您可以在 [= 之后写入它78=] 已过期。

Readwrite一起也可以防止'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 and write together also prevent 'b from shrinking, because of this scenario:

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 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 = ...;

{
    // 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!

还有一个问题

已经很久了,但回想一下:

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 已经成立!因此,仅当您希望 return 包含 '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 的实际生命周期 更小 的生命周期,即使指针在该生命周期之外!