当我在结构中使用可变引用而不是不可变引用时,为什么会出现生命周期错误?

jac*_*ang 2 lifetime rust

这段代码工作正常(Playground):

struct F<'a> {
    x: &'a i32,
}

impl<'a> F<'a> {
    fn get<'b>(&'b self) -> &'a i32 {
        self.x
    }
}

fn main() {
    let x = 3;
    let y = F { x: &x };
    let z = y.get();
}
Run Code Online (Sandbox Code Playgroud)

但是当我x改为成为一个可变引用时(Playground):

struct Foo<'a> {
    x: &'a mut i32,  // <-- `mut` added
}

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

fn main() {
    let mut x = 3;              // <-- `mut` added
    let y = Foo { x: &mut x };  // <-- `mut` added
    let z = y.get();
}
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
 --> src/main.rs:7:9
  |
7 |         self.x
  |         ^^^^^^
  |
note: ...the reference is valid for the lifetime 'a as defined on the impl at 5:6...
 --> src/main.rs:5:6
  |
5 | impl<'a> Foo<'a> {
  |      ^^
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 get(&self) -> &'a i32 {
7 | |         self.x
8 | |     }
  | |_____^
Run Code Online (Sandbox Code Playgroud)

为什么会这样?据我所知,生命周期的任何内容都没有改变:所有的值/引用仍然与第一个代码片段完全一样长.

Mat*_* M. 5

为什么Rust编译器拒绝这种实现get?因为它允许:

以下是完全合理的main,假设get编译:

fn main() {
    let mut x = 3;

    let y = Foo { x: &mut x };
    let a = y.get();
    let b = y.x;

    println!("{} {}", a, b);
}
Run Code Online (Sandbox Code Playgroud)

然而,如果get要编译,这将是好的:

  • a不借用y因为生命周期不同
  • b"消费" y(从中移动y.x)但我们之后不再重复使用

所以一切都很好,除了我们现在有一个&i32并且&mut i32 指向x.

注意:要使其编译,您可以使用unsafe内部get:unsafe { std::mem::transmute(&*self.x) }; 吓人,嗯?


借用检查算法的核心是Rust的内存安全性构建的基石:

别名XOR Mutability

Rust通过保证无论何时修改某些东西,没有观察者可以在可能变成悬空的东西内部引用来实现内存安全而不进行垃圾收集.

这反过来让我们解释:

  • &T作为别名参考 ; 它是Copy
  • &mut T作为一个独特的参考 ; 它不是Copy,因为它会违反唯一性,但它可以被移动

这种差异在这里节省了我们

由于&mut T不能被复制,只有这样,才能从去&mut T&T(或&mut T)是执行重新借用:解引用并采取到结果的引用.

这是由编译器隐式完成的.手动执行此操作会产生更好的错误消息:

fn get<'b>(&'b self) -> &'a i32 {
    &*self.x
}
Run Code Online (Sandbox Code Playgroud)
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
 --> <anon>:7:9
  |
7 |         &*self.x
  |         ^^^^^^^^
  |
help: consider using an explicit lifetime parameter as shown: fn get(&'a self) -> &'a i32
 --> <anon>:6:5
  |
6 |     fn get<'b>(&'b self) -> &'a i32 {
  |     ^
Run Code Online (Sandbox Code Playgroud)

为什么不能推断一生?因为重新借款的寿命受到限制,'b但是我们要求a 'a并且两者之间没有关系!

顺便说一下,这就是在这里拯救我们的错误,因为它确保在结果存在时Foo 必须借用实例(阻止我们使用可变引用Foo::x).

在编译器提示之后,返回&'b i32工作...并阻止上述main编译:

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

fn main() {
    let mut x = 3;

    let y = Foo { x: &mut x };
    let a = y.get();
    let b = y.x;

    println!("{} {}", a, b);
}
Run Code Online (Sandbox Code Playgroud)
error[E0505]: cannot move out of `y.x` because it is borrowed
  --> <anon>:16:9
   |
15 |     let a = y.get();
   |             - borrow of `y` occurs here
16 |     let b = y.x;
   |         ^ move out of `y.x` occurs here
Run Code Online (Sandbox Code Playgroud)

但是它让第一次main编译没有问题:

fn main() {
    let mut x = 3;

    let y = Foo { x: &mut x };
    let z = y.get();

    println!("{}", z);
}
Run Code Online (Sandbox Code Playgroud)

打印3.