可变引用生命周期与不可变引用生命周期

3

I have the following code:

struct Bar<T> {
    k: [T; 10],
}

impl<T> Bar<T> {
    fn thing(&self, i: usize) -> &T {
        &self.k[i]
    }

    fn thing_mut(&mut self, i: usize) -> &mut T {
        &mut self.k[i]
    }
}

struct Foo<'a, T: 'a> {
    bar: &'a Bar<T>,
    count: usize,
}

impl<'a, T> Foo<'a, T> {
    fn get(&mut self) -> Option<&'a T> {
        if self.count < 10 {
            let thing = self.bar.thing(self.count);
            self.count += 1;
            Some(thing)
        } else {
            None
        }
    }
}

struct FooMut<'a, T: 'a> {
    bar: &'a mut Bar<T>,
    count: usize,
}

impl<'a, T> FooMut<'a, T> {
    fn get(&mut self) -> Option<&'a mut T> {
        if self.count < 10 {
            let thing = self.bar.thing_mut(self.count);
            self.count += 1;
            Some(thing)
        } else {
            None
        }
    }
}

Rust playground

Foo可以编译,但FooMut无法编译:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:40:34
   |
40 |             let thing = self.bar.thing_mut(self.count);
   |                                  ^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 38:5...
  --> src/main.rs:38:5
   |
38 | /     fn get(&mut self) -> Option<&'a mut T> {
39 | |         if self.count < 10 {
40 | |             let thing = self.bar.thing_mut(self.count);
41 | |             self.count += 1;
...  |
45 | |         }
46 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:40:25
   |
40 |             let thing = self.bar.thing_mut(self.count);
   |                         ^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 37:1...
  --> src/main.rs:37:1
   |
37 | impl<'a, T> FooMut<'a, T> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: ...so that the expression is assignable:
           expected std::option::Option<&'a mut T>
              found std::option::Option<&mut T>

为什么不可变的编译正常,而可变的则不行?在“FooMut”的情况下,是否缺少某些生命周期注释?我看到了很多关于生命周期和引用的答案,但我特别询问可变与不可变的情况。

2
我相信你的问题已经被为什么只有可变引用才需要链接生命周期?回答了。如果不是这个问题,如何解决Rust中可变引用的生命周期错误?可变与不可变生命周期可能会回答它。请[编辑]你的问题,解释它与这些现有问题的区别。如果没有区别,我们可以将其标记为已回答。 - Shepmaster
1个回答

1
不可变和可变引用的生命周期方面已经在各个地方进行了涵盖:请参见问题的评论以及答案中嵌入的参考资料。
我在这里写一些笔记,重点关注这个特定情况,希望能够为 Rust 生命周期这个难懂的概念提供一些启示(至少对我来说是难懂的)。
考虑这个代码片段,这是一个简化版,展示了与问题相同的问题。
struct Foo<'a> {
    x: &'a mut i32,
}

impl<'b> Foo<'b> {
    fn x(&mut self) -> &'b mut i32 { self.x }
}

fn main() {
    let y = &mut 5;              // <- 'a(1)
    let mut f = Foo { x: y };    //    'a(1) <- 'b(2)
    println!("x is: {}", f.x()); //    'a(1)    'b(2) <- 'anonymous(3)
}

这里有三个生命周期正在发挥作用:
- `a(1)`,类型为`&mut i32`的y值的生命周期 - `b(2)`,类型为`Foo`的f值的生命周期 - `anonymous(3)`,编译器指定的`&self`引用的生命周期,因为在`fn x(&mut self) -> &'b i32`方法中没有为`&self`分配明确的生命周期值。
在文档中,通常会使用相同的字母对`struct`和`impl`上的生命周期泛型进行注释:
在这个例子中,我已经用`'a`注释了`struct`生命周期泛型,并用`'b`注释了`impl`,以明确编译器生成的具体生命周期与两个不同的范围相关联。
请查看上面示例代码中的注释以获取视觉表示。
如果我们尝试编译,我们会得到:
error[E0312]: lifetime of reference outlives lifetime of borrowed content...
 --> src/main.rs:6:30
  |
6 |     fn x(&self) -> &'b i32 { self.x }
  |                              ^^^^^^
  |
note: ...the reference is valid for the lifetime 'b as defined on the impl at 5:1...
 --> src/main.rs:5:1
  |
5 | impl<'b> Foo<'b> {
  | ^^^^^^^^^^^^^^^^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on     the method body at 6:5
 --> src/main.rs:6:5
  |
6 |     fn x(&self) -> &'b i32 { self.x }
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我们可以看到,'anonymous 生命周期比 'b 更短(请参见代码注释中的“近似”生命周期可视化):借用内容 self.x 的寿命不足以满足 Rust 安全规则。
现在清楚了,解决方案应该是通过显式注释减少生命周期,或者更好地支持省略规则。
struct Foo<'a> {
    x: &'a mut i32,
}

impl<'b> Foo<'b> {
    fn x(&mut self) -> &mut i32 { self.x }
}

fn main() {
    let y = &mut 5;              // <- 'a
    let mut f = Foo { x: y };    //    'a <- 'b
    println!("x is: {}", f.x()); //    'a    'b
}

现在这个代码片段可以编译,从这个 答案 中学到的教训应该是:

一个经验法则:不要随处滥用单一的生命周期。只有当需要相同的生命周期时才使用相同的生命周期。

不可变引用

好的,但是如果 Foo::x 是一个不可变引用,为什么编译器不会抛出错误呢?

简短的回答是:

如果内部引用是不可变的,编译器就可以确保在缩小生命周期范围时不会出现任何内存问题。

相反,在内部可变性的情况下(在这种情况下防止 'anonymous != 'b),编译器会防止不同的生命周期跨度,因为如果对 Foo 的引用的生命周期(&mut self)和对 &mut i32 的引用的生命周期(self.x)不相等,则可能会发生无效的内存状态。

为了帮助理解内部引用可变性和生命周期缩小可能会发生的事情,请考虑以下无效的 rust 代码片段:

let mut my_ref: &mut i32 = &mut 1;
let mut f = Foo { x: my_ref }; 
{                                 | <--- narrowed lifetime scope
    let y = &mut 5;               |
    f.x = y;                      |
}                                 | <---
// ERROR: invoking f.x() when self.x is no more valid!
f.x();

请参阅this answer以获取更多详细信息。

谢谢你的回答。如果内部引用是不可变的,编译器可以确保在缩小生命周期范围时不会出现任何内存问题。"缩小生命周期范围"是什么意思?self.x不应该具有f实例的生命周期吗?哪个变量的生命周期范围被缩小了? - Helin Wang
self.x(指向self.x的引用的生命周期)和作为Foo::x(&mut self)的&self参数的引用可能具有不同的生命周期,因此需要缩小生命周期范围。请查看我的更新以了解更清晰的含义。 - attdona

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接