Search code examples
rustreferencelifetimemutable

Lifetime when exchanging reference


I'm learning Rust and play through some scenarios to see how it behaves. When passing a mutable reference to a function and then assign another mutable reference to it, it seems like the Rust compiler wants the referenced variable to live as long as the one originally referenced.

However, as I am literally exchanging the reference to point somewhere else, it would never affect the reference of the calling function. It will always still point to foo, which is perfectly alive. Why does foo2 have to live as long as foo then? This seems like it expects something like replacing the reference in the original reference ...

struct Foo {
    x: u32,
    y: u32
}

fn do_something(mut foo: &mut Foo) {
    foo.x = 4;
    foo.y = 5;
    println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
    let mut foo2 = Foo{x: 10, y: 20};
    foo = &mut foo2;
    println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
}

fn main() {
    let mut foo = Foo{x: 1, y: 2};
    do_something(&mut foo);
    println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
}

This is what the compiler says:

error[E0597]: `foo2` does not live long enough
  --> main.rs:11:11
   |
6  | fn do_something(mut foo: &mut Foo) {
   |                          - let's call the lifetime of this reference `'1`
...
10 |     let mut foo2 = Foo{x: 10, y: 20};
   |         -------- binding `foo2` declared here
11 |     foo = &mut foo2;
   |     ------^^^^^^^^^
   |     |     |
   |     |     borrowed value does not live long enough
   |     assignment requires that `foo2` is borrowed for `'1`
12 |     println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
13 | }
   | - `foo2` dropped here while still borrowed

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

Solution

  • The problem is that the type of &mut Foo includes a lifetime, and that lifetime is bigger than that of foo2. This is a problem, because you the compiler assumes that lifetime is big, and you can exploit that. For example:

    fn do_something(mut foo: &mut Foo) -> &mut Foo {
        foo.x = 4;
        foo.y = 5;
        println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
        let mut foo2 = Foo{x: 10, y: 20};
        foo = &mut foo2;
        println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
        foo
    }
    

    Here, due to lifetime elision, the lifetime of the return type is the same as the lifetime of foo. But we return foo: this must be okay. However, this code is not okay: it clearly has a use-after-free. The only valid conclusion is that the assignment foo = &mut foo2 is invalid.

    If you declare a new variable, that its lifetime is shorter, it will work:

    fn do_something(foo: &mut Foo) {
        let mut foo = foo;
        foo.x = 4;
        foo.y = 5;
        println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
        let mut foo2 = Foo{x: 10, y: 20};
        foo = &mut foo2;
        println!("foo.x = {}\nfoo.y = {}", foo.x, foo.y);
    }