为啥链接生命周期仅与可变引用有关?

Posted

技术标签:

【中文标题】为啥链接生命周期仅与可变引用有关?【英文标题】:Why does linking lifetimes matter only with mutable references?为什么链接生命周期仅与可变引用有关? 【发布时间】:2015-11-16 21:49:45 【问题描述】:

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

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 &lt; 'a,因此违反了 VecRefRef&lt;'a&gt; 中的约束,与 VecRef&lt;'a&gt; 的生命周期完全相同。

我将可变引用的生命周期与VecRef&lt;'a&gt;中的借用数据联系起来:

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

现在可以了。但为什么?我怎么能提供这样的参考? create() 内的可变引用 r 的生命周期为 VecRef&lt;'a&gt; 而不是 'a。为什么问题没有推到函数create()的调用方?

我注意到另一件事我不明白。如果我在VecRefRef&lt;'a&gt; 结构中使用不可变 引用,那么在提供具有不同生命周期'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&lt;'a&gt;VecRef&lt;'a&gt; 进行可变引用的第一个示例相反。我知道可变引用有不同的别名规则(根本没有别名),但这与这里的链接生命周期有什么关系?

【问题讨论】:

【参考方案1】:

警告:我说的是我并不真正具备的专业水平。鉴于这篇文章的长度,我可能错了很多次。

TL;DR:***值的生命周期是协变的。引用值的生命周期是不变的。

介绍问题

您可以通过将VecRef&lt;'a&gt; 替换为&amp;'a mut T 来显着简化示例。

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

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

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

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

旁白:我说“显式注释”是因为in implicit cases like let x = y or fn f&lt;T&gt;(_: T) , reborrowing doesn't seem to happen。目前尚不清楚这是否是有意的。

完整的例子是

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

能够生成&amp;'a mut &amp;'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);

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

不明显

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

这是否比&amp;'a mut &amp;'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 的生命周期无关。

hard case - 指针指针

用指针指针考虑一些堆栈位置:

    ║ 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 阻止'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!

所以,'b 不能收缩,也不能从 'a 增长,所以 'a == 'b 完全一样。这意味着 &amp;'y mut &amp;'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 ()

外层&amp;'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);

这个成功了。为什么?

好吧,考虑一下我们从以前缩小&amp;'b 的问题:

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 完全一样。

这只会发生,因为我们可以将内部引用换成一些新的、寿命不够长的引用。如果我们不能交换参考,这不是问题。因此缩短内部引用的生命周期是可能的。

那失败的呢?

使内部引用不可变并没有帮助:

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

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

不明显

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

这是否比&amp;'a mut &amp;'a mut ()好。

我希望回答这些问题。

我们已经回答了第一个重点问题,但第二个问题呢? 'a: 'b 是否允许超过 'a == 'b

考虑一些类型为&amp;'x mut &amp;'y mut () 的调用者。如果是'x : 'y,那么它将自动转换为&amp;'y mut &amp;'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 的实际生命周期更小的生命周期,即使指针超出该生命周期!

【讨论】:

Read 防止'b 增长,因为如果要从外部指针中提取内部指针,您将能够在'a 过期后读取它。 -- 能否请您详细说明一下? @soupybionics 我怀疑我错了。让我回复你。 @soupybionics 抱歉,我似乎忘记了您的问题。我记得我看过它并认为我错了,但我不记得细节了。我会注意到 &amp;'static &amp;'b 可以 转换为 &amp;'static &amp;'static 这表明你是对的,但我认为这是不正确的。相反,可能有一个隐含的假设:'b: 'a;将&amp;'a &amp;'b 转换为&amp;'a &amp;'static 失败。【参考方案2】:

create() 内部的可变引用 r 的生命周期为 VecRef&lt;'a&gt; 而不是 '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

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

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

【讨论】:

我明白了。在 create 函数的主体中,选择的生命周期将是 2-3 的范围,因为从提供的一组具体生命周期中,它将是唯一与约束匹配的生命周期。因此,它将是创建的 VecRefRef. 的生命周期 'a 请注意,您必须使用语法'b : 'a 来表示'a 的生命周期将超过'b. — 不应该是“...表示'b 的寿命将超过 'a。” ?

以上是关于为啥链接生命周期仅与可变引用有关?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们可以非常量引用临时对象并延长其生命周期?

JAVA对象生命周期-对象的销毁

Android-Android中service与application的生命周期有关系吗

Android-Android中service与application的生命周期有关系吗

为啥我们使用 - ngAfterContentInit 生命周期方法

存储类生命周期作用域链接域