gpt4 book ai didi

rust - 为什么链接生存期仅与可变引用有关?

转载 作者:行者123 更新时间:2023-12-05 01:14:36 28 4
gpt4 key购买 nike

几天前,在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

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

生存期 'b类似于 'b < 'a,因此违反了 VecRefRef<'a>中的约束,使其与被称为 VecRef<'a>的生存期完全相同。

我将可变引用的生存期与 VecRef<'a>中的借入数据关联起来:
fn create<'a>(r: &'a mut VecRef<'a>) {
VecRefRef(r);
}

现在可以了。但为什么?我什至还能提供这样的引用? r中的可变引用 create()具有 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>。我知道可变引用具有不同的别名规则(根本没有别名),但这与链接的生存期有什么关系?

最佳答案

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 in implicit cases like let x = y or fn f<T>(_: T) {}, reborrowing doesn't seem to happen. 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);
    }

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

    使内部引用不可变根本没有帮助!
    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仍然非常有用。

    在您了解此处的行为之前,了解方差的概念非常重要。如果您已经修读了涵盖此内容的大学类(class),或者可以从其他背景下记住它,那么您的处境就很好。不过,您可能仍然会喜欢将想法与终身联系起来的帮助。

    简单的情况-普通指针

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

        ║ 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的有效期限是多少?

    考虑两个可变的指针操作:
  • 阅读

  • 读取可以防止 'y增长,因为 'x引用仅保证对象在 'x范围内保持 Activity 状态。但是, 读取不会阻止 'y缩小,因为在指向值处于 Activity 状态时进行的任何读取都将导致独立于生存期 '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

    考虑到你做
    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:
  • 阅读

  • 读取可以防止 'y增长,因为 'x引用仅保证对象在 'x范围内保持 Activity 状态。但是, 读取不会阻止 'y缩小,因为在指向值处于 Activity 状态时进行的任何读取都将导致独立于生存期 '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!

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

    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已经成立!因此,仅当您希望将包含 '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实际生存期的生存期!

    关于rust - 为什么链接生存期仅与可变引用有关?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55849717/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com