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

时间:2017-01-01 16:37:13

标签: rust lifetime

此代码正常(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();
}

但当我将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();
}

我收到此错误:

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

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

1 个答案:

答案 0 :(得分: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);
}

然而,如果编译get,那就没关系了:

  • a不会借用y因为生命周期不同
  • b“消耗”y(从y.x移出)但我们不会在
  • 之后重复使用它

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

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

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

  

别名XOR可变性

Rust通过保证每当你修改某些内容时,没有任何垃圾收集来实现内存安全,没有任何观察者可以在内部引用可能会变成悬空的内容。

这反过来让我们解释:

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

这种差异在这里拯救了我们。

由于无法复制&mut T,因此从&mut T转到&T(或&mut T)的唯一方法是执行重新借用:取消引用并取消参考结果。

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

fn get<'b>(&'b self) -> &'a i32 {
    &*self.x
}
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 {
  |     ^

为什么不能推断一生?因为重新借用的生命周期受到'b的限制,但我们需要'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);
}
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

然而,它允许第一个main编译没有问题:

fn main() {
    let mut x = 3;

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

    println!("{}", z);
}

打印3