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

jte*_*epe 15 rust

前几天,有一个问题,如果有人有一个问题,一个可变引用其中包含借来的数据本身就是一种类型的连接寿命.问题是提供对类型的引用,借用与类型内部借用数据相同的生命周期.我试图重新创建问题:

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);
}
Run Code Online (Sandbox Code Playgroud)

示例代码

'b在这里明确注释了create().这不编译:

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
Run Code Online (Sandbox Code Playgroud)

寿命'b是类似的'b < 'a,因此违反了VecRefRef<'a>与所提到的完全相同的生命周期中的约束VecRef<'a>.

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

fn create<'a>(r: &'a mut VecRef<'a>) {
    VecRefRef(r);
}
Run Code Online (Sandbox Code Playgroud)

现在它有效.但为什么?我怎么能提供这样的参考?r里面的可变引用create()的生命周期VecRef<'a>不是'a.为什么问题没有推到函数的调用端create()

我注意到另一件我不理解的事情.如果我在struct中使用不可变引用VecRefRef<'a>,那么在提供具有不同生命周期的引用时,它在某种程度上无关紧要'a:

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);
}
Run Code Online (Sandbox Code Playgroud)

示例代码

这与第VecRefRef<'a>一个带有可变引用的示例相反VecRef<'a>.我知道可变引用有不同的别名规则(根本没有别名)但是这与链接的生命周期有什么关系呢?

Vee*_*rac 15

警告:我说的是我不具备的专业水平.考虑到这篇文章的篇幅,我可能错了很多次.

TL; DR:顶级值的生命周期是逆变的.参考值的生命周期是不变的.

介绍问题

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

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

VecRefRef我们不使用构造函数,而是使用此函数:

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}
Run Code Online (Sandbox Code Playgroud)

在我们走得更远之前,了解如何在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);
}
Run Code Online (Sandbox Code Playgroud)

这给出了同样的错误:

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
Run Code Online (Sandbox Code Playgroud)

一个微不足道的修复

人们可以通过这样做来解决它

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);
}
Run Code Online (Sandbox Code Playgroud)

因为签名现在在逻辑上是相同的.然而,不明显的是为什么

let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;

use_ref_ref(ref_ref);
Run Code Online (Sandbox Code Playgroud)

是能够生产出来的&'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);
}
Run Code Online (Sandbox Code Playgroud)

这意味着外部参考的寿命至少与内部参考的寿命一样大.

这并不明显

  • 为什么&'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);
}
Run Code Online (Sandbox Code Playgroud)

另一个更令人惊讶的修复

使外部引用不可变修复了问题

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);
}
Run Code Online (Sandbox Code Playgroud)

还有一个更令人惊讶的非修复!

使内部引用不可变根本没有用!

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);
}
Run Code Online (Sandbox Code Playgroud)

但为什么??!

原因是......

等等,首先我们介绍方差

计算机科学中的两个非常重要的概念是协方差逆变.我不打算使用这些名称(我会非常明确地指出我正在构建的东西),但这些名称对于搜索互联网仍然非常有用.

在理解这里的行为之前,理解方差的概念非常重要.如果您参加了涵盖此课程的大学课程,或者您可以从其他方面记住它,那么您就处于有利位置.不过,您可能仍然感谢将这个想法与生命期联系起来的帮助.

简单的情况 - 一个普通的指针

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

    ? Name      ? Type                ? Value
 ?????????????????????????????????????????????
  1 ? val       ? i32                 ? -1
 ?????????????????????????????????????????????
  2 ? reference ? &'x mut i32         ? 0x1
Run Code Online (Sandbox Code Playgroud)

堆栈向下增长,因此reference堆栈位置在之后创建val,并且将在之前被删除val.

考虑一下你做的

let new_ref = reference;
Run Code Online (Sandbox Code Playgroud)

要得到

    ? Name      ? Type        ? Value  
 ????????????????????????????????????? 
  1 ? val       ? i32         ? -1     
 ????????????????????????????????????? 
  2 ? reference ? &'x mut i32 ? 0x1    
 ????????????????????????????????????? 
  3 ? new_ref   ? &'y mut i32 ? 0x1    
Run Code Online (Sandbox Code Playgroud)

一生的寿命有效'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    
Run Code Online (Sandbox Code Playgroud)

考虑一下你做的

let new_ref_ref = ref_ref;
Run Code Online (Sandbox Code Playgroud)

要得到

    ? 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    
Run Code Online (Sandbox Code Playgroud)

现在有两个问题:

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

{
    // 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!
Run Code Online (Sandbox Code Playgroud)

因此,'b不能缩小,也不能从中生长'a,所以确实如此'a == '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);
}
Run Code Online (Sandbox Code Playgroud)

当你打电话时use_same_ref_ref,会尝试演员

&'a mut &'b mut ()  ?  &'c mut &'c mut ()
Run Code Online (Sandbox Code Playgroud)

现在请注意,'b == 'c因为我们讨论了方差.因此我们实际上是在施法

&'a mut &'b mut ()  ?  &'b mut &'b mut ()
Run Code Online (Sandbox Code Playgroud)

外层&'a只能收缩.为此,编译器需要知道

'a: 'b
Run Code Online (Sandbox Code Playgroud)

编译器不知道这一点,因此编译失败.

那我们的其他例子呢?

第一个是

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);
}
Run Code Online (Sandbox Code Playgroud)

而不是'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);
}
Run Code Online (Sandbox Code Playgroud)

第三个断言 '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);
}
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为这不是必需的断言.

不变性呢?

我们这里有两个案例.第一个是使外部引用不可变.

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);
}
Run Code Online (Sandbox Code Playgroud)

这个工作.为什么?

好吧,考虑一下我们&'b之前收缩的问题:

阅读在一起,还可以防止'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!
Run Code Online (Sandbox Code Playgroud)

因此,'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);
}
Run Code Online (Sandbox Code Playgroud)

当您考虑到之前提到的问题从未涉及内部引用的任何读取时,这是有道理的.实际上,这里修改了有问题的代码来证明:

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!
Run Code Online (Sandbox Code Playgroud)

还有一个问题

它已经很长了,但请回想一下:

人们可以改为强制执行 '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);
}
Run Code Online (Sandbox Code Playgroud)

这意味着外部参考的寿命至少与内部参考的寿命一样大.

这并不明显

  • 为什么&'a mut &'b mut ()不能施展&'c mut &'c mut (),或

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

我希望回答这些问题.

我们已经回答了第一个有问题的问题,但第二个问题呢?是否'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);
Run Code Online (Sandbox Code Playgroud)

在哪里use_ref_ref定义

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}
Run Code Online (Sandbox Code Playgroud)

代码如何执行'a: 'b?它看起来像是相反的检查是真的!

好吧,记住那个

let reference = &mut val;
Run Code Online (Sandbox Code Playgroud)

能够缩短其寿命,因为它是此时的外部寿命.因此,即使指针位于该生命周期之外,它也可以指小于实际生命周期的val生命周期!


She*_*ter 5

所述可变参考r内部create()具有的寿命VecRef<'a>'a

这是造成混乱的常见原因。检查此函数定义:

fn identity<'a, T>(val: &'a T) -> &'a T { val }
Run Code Online (Sandbox Code Playgroud)

在函数定义中,'a通用生命周期参数,它与通用类型参数(T)并行。调用该函数时,调用者决定'aand 的具体值T。让我们回头看看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 |  |
}
Run Code Online (Sandbox Code Playgroud)

v将在main(1-3)的整个过程中有效,但ref_v仅在两个最终声明(2-3)中有效。请注意,它ref_v 指的是一个超出其寿命的值。如果随后引用ref_v,则引用的是(2-3)的生命,而引用的本身是(1-3)的生命。

检查您的固定方法:

fn create<'a>(r: &'a mut VecRef<'a>)
Run Code Online (Sandbox Code Playgroud)

这表示对于此函数调用,对VecRef和包含的引用的引用必须相同。可以选择满足此要求的寿命-(2-3)。

请注意,当前您的结构定义要求两个生存期都相同。您可以允许它们有所不同:

struct VecRefRef<'a, 'b: 'a>(&'a mut VecRef<'b>);
fn create<'a, 'b>(r: &'a mut VecRef<'b>)
Run Code Online (Sandbox Code Playgroud)

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

如果我使用不可变的引用,它就不再重要了

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