首页 > 解决方案 > 可变引用生命周期与不可变引用生命周期

问题描述

我有以下代码:

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

锈操场

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?我已经看到很多关于生命周期和参考的答案,但我在这种情况下特别询问可变与不可变的情况。

标签: referencerustimmutabilitymutablelifetime

解决方案


不可变和可变引用的生命周期方面已经在不同的地方进行了介绍:请参阅问题的注释和答案中嵌入的引用。

我在这里写了一些关注这个特定案例的笔记,希望能够阐明 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)类型的 y 值的生命周期&mut i32
  • 'b(2)类型的 f 值的生命周期Foo
  • 'anonymous(3)编译器分配给引用的生命周期,因为在方法&self中没有分配明确的生命周期值。&selffn x(&mut self) -> &'b i32

在文档中,生命周期泛型通常使用相同的字母进行注释:在此示例中,我已将生命周期泛型注释为structwith和with以证明编译器生成的具体生命周期与两个不同的跨度相关联。implstruct'aimpl'b

请参阅上面示例代码中的注释以获取可视化表示。

如果我们尝试编译,我们会得到:

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不足以满足防锈安全规则。

现在很明显,解决方案应该是通过显式注释来减少生命周期,或者在省略规则的支持下更好地减少生命周期:

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

有关更多详细信息,请参阅此答案


推荐阅读