当前位置: 首页 > 知识库问答 >
问题:

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

养焱
2023-03-14

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

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

示例代码

我在 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

我将可变引用的生存期与VecRef中的借用数据链接起来

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

现在成功了。但是为什么呢?我怎么能提供这样的参考呢?< code>create()内的可变引用< code>r的生存期为< code>VecRef

我注意到另一件我不明白的事情。如果我在VecRefRef中使用不可变引用

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

示例代码

这与VecRefRef的第一个示例相反。


共有2个答案

池庆
2023-03-14

< code>create()内的可变引用< code>r的生存期为< code>VecRef

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

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)中有效。请注意,<code>ref_v</code>指的是一个超过它的值。如果您随后引用<code>ref_v</code>,则您有一个引用来自(2-3)的对象,而它本身也有一个参考来自(1-3)。

查看您的固定方法:

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

这意味着对于这个函数调用,对<code>VecRef</code>的引用和它包含的引用必须相同。可以选择满足以下条件的寿命-(2-3)。

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

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

请注意,您必须使用语法< code>'b: 'a来表示生存期< code>'b将比< code>'a长。

如果我使用一个不可变的引用[…],不知何故它不再重要

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

盖晋
2023-03-14
匿名用户

警告:我所说的是我真正没有的专业水平。鉴于这篇文章的篇幅,我可能错了很多次。

太长别读:顶级值的生命周期是协变的。引用值的生命周期是不变的。

通过替换< code>VecRef,可以大大简化您的示例

此外,应该去掉< code>main,因为讨论函数的一般行为比讨论某个特定的生存期实例化更完整。

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

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

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

旁白:我说“显式注释”是因为在像< code>let x = y或< code>fn f这样的隐式情况下

然后,完整的示例是

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

能够生成一个<代码>

可以改为强制< code>'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);
}

这意味着外部基准的寿命至少与内部基准的寿命一样大。

这并不明显

> < li>

为什么< code >

这是否优于<code>

我希望回答这些问题。

断言 '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

堆栈向下增长,因此参考堆栈位置是在 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防止<code>‘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    

现在有两个问题:

> < li>

什么生存期对< code>'y有效?

什么生存期对< code>'b有效?

让我们首先考虑< code>'y的两个可变指针操作:

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

Write防止<code>‘y'y收缩,因为对指针的任何写入都会复制中的值,这使得它与'y的生存期无关。

这和以前一样。

读取可防止'b增长,因为如果要从外部指针中提取内部指针,则可以在'a过期后读取它。

Write还可以防止< code>'b增长,因为如果要从外部指针中提取内部指针,您就可以在< code>'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!

因此,< code>'b不能收缩,也不能从< code>'a增长,所以< code>'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);
}

当您调用use_same_ref_ref时,将尝试强制转换

&'a mut &'b mut ()  →  &'c mut &'c mut ()

现在注意< code>'b == 'c,因为我们讨论的是方差。因此我们实际上是在选角

&'a mut &'b mut ()  →  &'b mut &'b mut ()

外部

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

第三个断言< code>'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);
}

这个成功了。为什么?

好吧,考虑一下我们关于收缩< code >的问题

由于这种情况,一起读取和写入还可以防止'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!

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

可以改为强制< code>'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);
}

这意味着外部基准的寿命至少与内部基准的寿命一样大。

这并不明显

>

  • 为什么<代码>

    这是否优于<code>

    我希望回答这些问题。

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

    考虑一些类型为<code>的调用者

    如果你写

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

    代码如何能够强制< code>'a: 'b?仔细看起来,情况恰恰相反!

    嗯,记住这一点

    let reference = &mut val;
    

    能够缩短它的寿命,因为此时它是外层寿命。因此,它可以引用比< code>val的实际生存期更短的生存期,即使指针不在该生存期内!

  •  类似资料:
    • 当在第四章讨论引用时,我们遗漏了一个重要的细节:Rust 中的每一个引用都有其 生命周期(lifetime),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的

    • 下面是一个资源借用的例子: fn main() { let a = 100_i32; { let x = &a; } // x 作用域结束 println!("{}", x); } 编译时,我们会看到一个严重的错误提示: error: unresolved name x. 错误的意思是“无法解析 x 标识符”,也就是找不到 x , 这是因为像很多编

    • 本文向大家介绍函数式组件有没有生命周期?为什么?相关面试题,主要包含被问及函数式组件有没有生命周期?为什么?时的应答技巧和注意事项,需要的朋友参考一下 没有生命周期 因为他没有继承React.Component 所以也不需要render()

    • 我对Spring的生命周期感到困惑。 上面的代码片段是否创建了对象? 如果上述答案为真。 a) 然后,对于作用域为“singleton”的bean,获取在上述代码片段中创建的对象。我是对还是错? b)对于范围为“原型”的情况,创建的对象是否未使用。因为,容器总是返回新对象。 上面的代码片段是否创建了对象? 如果答案是假的, Spring框架如何验证bean定义是否正确。 根据亨利的回答 通常,单例

    • 问题内容: 在哪里进行调用将使我的状态失水的API调用的最佳位置是哪里?构造函数或生命周期方法之一,例如ComponentWillMount? 问题答案: 最好从生命周期方法进行api调用,反应文档也建议相同。 根据DOC: componentDidMount: 挂载组件后立即调用componentDidMount()。需要DOM节点的初始化应该在这里进行。 如果需要从远程端点加载数据,这是实例化

    • 本文向大家介绍React16废弃了哪些生命周期?为什么?相关面试题,主要包含被问及React16废弃了哪些生命周期?为什么?时的应答技巧和注意事项,需要的朋友参考一下 React16废弃的生命周期有3个will: componentWillMount componentWillReceiveProps componentWillUpdate 废弃的原因,是在React16的Fiber架构中,调和过