首页 文章

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

提问于
浏览
12

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

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> 结构中使用不可变引用,那么在提供具有不同生命周期 '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);
}

Example code

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

2 回答

  • 13

    create()中的可变引用r的生命周期为VecRef <'a>而不是'a'

    这是混淆的常见原因 . 检查此功能定义:

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

    在函数定义中, 'a 是一个通用的生命周期参数,它与泛型类型参数( 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 .

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

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

  • 4

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

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

    介绍问题

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

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

    而不是 VecRefRef 's constructor, let'使用此功能:

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

    在我们走得更远之前,了解生命周期如何在Rust中隐式转换是很重要的 . 当指定另一个显式注释名称的指针时,就会发生生命周期强制 . 这允许最明显的事情是缩小顶级指针的生命周期 . 因此,这不是一个典型的举动 .

    旁白:我说“明确注释”,因为在隐式情况下,例如让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);
    }
    

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

    这并不明显

    • 为什么 &'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);
    }
    

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

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

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

    但是为什么??!

    原因是......

    坚持,首先我们覆盖方差

    计算机科学中的两个非常重要的概念是 covariancecontravariance . 我应该非常明确地指出我正在投掷的东西,但这些名称对于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

    • Write

    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
    

    现在有两个问题:

    • 'y 的有效期是多少?

    • 'b 的有效期是多少?

    让我们首先考虑 y 与两个可变指针操作:

    • Read

    • Write

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

    Write 也阻止了 'y 的增长,因为无法写入无效的指针 . 但是, write 不会阻止 'y 收缩,因为对指针的任何写入都会复制该值,这使得它的生命周期保持不变 'y .

    这和以前一样 .

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

    Read 阻止 'b 增长,因为如果要从外部指针中提取内部指针,您将能够在 'a 过期后读取它 .

    Write 也阻止了 'b 的增长,因为如果要从外部指针中提取内部指针,你可以在 'a 过期后写入它 .

    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!
    

    Ergo, 'b cannot shrink and it cannot grow from 'a, so 'a == 'b exactly.

    好的,这是否解决了我们的问题?

    还记得代码吗?

    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 的问题:

    一起读写也可以防止'b缩小,因为这种情况:让ref_ref:&'x mut&'a mut i32 = ...;

    {
    //生命周期'b,小于'a
    让new_val:i32 = 123;

    //收缩'a到'b
    让new_ref_ref:&'x mut&'b mut i32 = ref_ref;

    • new_ref_ref =&mut new_val;
      }

    // new_ref_ref超出范围,因此ref_ref再次可用
    让ref_ref:&'a mut i32 = * ref_ref;
    //哎呀,我们有一个&'一个mut i32指针指向一个删除值!
    因此,'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>(参考:&'c mut&'c mut()){}

    fn use_ref_ref <'a:'b,'b>(参考:&'a mut&'b mut()){
    use_same_ref_ref(参考);
    }
    这意味着外部参考的寿命至少与内部参考的寿命一样大 . 这并不明显为什么''mut&'b mut()不能转换为&'c mut&'c mut(),或者这是否优于''mut&'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);
    

    其中 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 的实际生命周期的生命周期!

相关问题