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

3

这段代码在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 | |     }
  | |_____^

为什么会发生这种情况?就我看来,与生命周期有关的因素并没有改变:所有的值/引用仍然像第一个代码片段中一样精确地存在。


我不确定,但我认为第二个例子失败很重要,因为你无法再改变f的x。 - torkleyy
注意:f 应该是 F,而且你可以直接调用 y.get()... 我真的很喜欢这个问题! - Matthieu M.
哇,花了我一些时间,但我认为我终于明白这里到底发生了什么,而且深入探究真的很酷。再次感谢你的问题! - Matthieu M.
1个回答

7
为什么 Rust 编译器会拒绝这个 get 方法的实现?因为它允许以下情况发生:
假设 get 已经编译通过,下面是一个完全合理的 main 方法:
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移动),但我们之后不会再次使用它

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

注意:为了使其编译通过,您可以在get中使用unsafeunsafe { 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


有没有任何关于将&mut T分配给&T时“取消引用并获取结果的引用”的文档? - updogliu
@updogliu:我不知道有没有;但我还没有读最新版本的Rust书籍,等等... - Matthieu M.
let sss: &mut String = mut_ref_s; //从mut_ref_s重新借用 dummy(mut_ref_s); //将新重新借用的tmp从mut_ref_s移动 let ss = mut_ref_s; //移动mut_ref_s; ``` 请问有人可以解释编译器何时会选择重新借用或移动可变引用吗?谢谢! - Ze Gao
因为我发现在这里生成的ss和sss的MIR非常不同。https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6d36c270f1f0ab174f8a42027fae1c5a - Ze Gao
@ZeGao:正如您可能已经注意到的那样,评论区不太适合支持代码片段;请转到聊天室(https://chat.stackoverflow.com/rooms/62927/rust)或发布一个新的问题。 - Matthieu M.

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