jackbang
jackbang

Reputation: 41

Why do I get a lifetime error when I use a mutable reference in a struct instead of an immutable reference?

This code works fine (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();
}

But when I change x to be a mutable reference instead (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();
}

I get this error:

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

Why does that happen? As far as I see it, nothing about the lifetimes has changed: all values/references still live exactly as long as in the first code snippet.

Upvotes: 4

Views: 804

Answers (1)

Matthieu M.
Matthieu M.

Reputation: 300409

Why does the Rust compiler reject this implementation of get? Because it allows:

The following is a perfectly reasonable main, assuming that get compiles:

fn main() {
    let mut x = 3;

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

    println!("{} {}", a, b);
}

Yet if get were to compile, this would be fine:

  • a does not borrow y because the lifetime is different
  • b "consumes" y (moving from y.x) but we do not reuse it after

So everything is fine, except that we now have a &i32 and &mut i32 both pointing to x.

Note: to make it compile, you can use unsafe inside of get: unsafe { std::mem::transmute(&*self.x) }; scary, eh?


At the heart of the borrow-checking algorithm is the cornerstone on which Rust's memory safety is built:

Aliasing XOR Mutability

Rust achieves memory safety without garbage collection by guaranteeing that whenever you are modifying something, no observer can have a reference inside that something that could become dangling.

This, in turns, lets us interpret:

  • &T as an aliasing reference; it is Copy
  • &mut T as a unique reference; it is NOT Copy, as it would violate uniqueness, but it can be moved

This difference saved us here.

Since &mut T cannot be copied, the only way to go from &mut T to &T (or &mut T) is to perform a re-borrowing: dereference and take a reference to the result.

This is done implicitly by the compiler. Doing it manually makes for a somewhat better error message:

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 {
  |     ^

Why cannot it infer a lifetime? Because lifetime of the re-borrow is limited by 'b but we are requiring a 'a and there's no relationship between the two!

By the way, this is what is saving us from blunder here, because it ensures that the instance Foo must be borrowed while the result lives (preventing us to use a mutable reference via Foo::x).

Following the compiler hint, and returning &'b i32 works... and prevents the above main from compiling:

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

However it lets the first main compile without issue:

fn main() {
    let mut x = 3;

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

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

Prints 3.

Upvotes: 7

Related Questions