Search code examples
rustreferencetemporary

How are Rust immutable and mutable references to temp values dropped?


First, I know there are many similar questions, and I've read many discussions, but I still cannot understand what's going on in my case.

struct Test {
    x: u32,
}

fn main() {
    let mut t = & mut Test { x: 0 };
    println!("{}", t.x);
    t = & mut Test { x: 1 };
    println!("{}", t.x);
}

Ok, this gives an error as expected:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:8:15
  |
8 |     t = & mut Test { x: 1 };
  |               ^^^^^^^^^^^^^- temporary value is freed at the end of this statement
  |               |
  |               creates a temporary value which is freed while still in use

creates a temporary value which is freed while still in use – that is right, but why doesn't this error happen before? It happens only on on the second assignment of &mut, whereas the first one is fine. Shouldn't it also create a temporary value that is dropped? This is what I can't understand.

Moreover, if I remove mut then everything runs fine:

fn main() {
    let mut t = & Test { x: 0 };
    println!("{}", t.x);
    t = & Test { x: 1 };
    println!("{}", t.x);
}

But why? A non-mutable reference is still a reference, it still points to some data, and the temporary values are still dropped. So references should be invalid. Should also give error, but no, it doesn't. Why?

But wait, there's more! If I implement Drop trait for Test:

impl Drop for Test {
    fn drop(& mut self) { }
}

And now I get the same error again, without changing the code in main(). How is it related?


Solution

  • Why

    let mut t = &mut Test { x: 0 };
    

    works is described in Why is it legal to borrow a temporary? and the reference:

    The temporary scopes for expressions in let statements are sometimes extended to the scope of the block containing the let statement. This is done when the usual temporary scope would be too small, based on certain syntactic rules.

    t = &mut Test { x: 1 };
    

    doesn't work because it's not a let statement and therefore not eligible for the same lifetime promotion.

    let mut t = &Test { x: 0 };
    

    allows the temporary to be promoted to a static (because it's immutable) and the following is eligible for constant promotion, too:

    t = &Test { x: 1 };
    

    so it works.

    Since a Drop implementation changes semantics of a static vs a local variable anything that implements it isn't eligible for constant promotion so the problem reappears (references to things that implement Drop are only eligible for the same scope extension as mutable references). The linked reference article contains a more comprehensive explanation of all of this.