My expectation is that the mutable reference another should exist for the entire scope of the fn main. Is the compiler aware that we never use the mutable borrow another AFTER we modify the owner of Mog? My understanding of reference lifetimes is the following:
I expected another to live through the end of fn main, therefore, making mog.name ="Back Again".to_string(); to fail because we still have a life mutable reference.
error[E0506]: cannot assign to `mog.name` because it is borrowed
--> src/main.rs:10:5
|
8 | let another = &mut mog;
| -------- borrow of `mog.name` occurs here
9 | another.name = "Kim".to_string();
10 | mog.name = "Back Again".to_string();
| ^^^^^^^^ assignment to borrowed `mog.name` occurs here
11 | another.name = "Switcheroo".to_string();
| ------------ borrow later used here
For more information about this error, try `rustc --explain E0506`.
error: could not compile `playground` due to previous error
Which reveals that I have some misunderstanding about how Rust handles reference lifetimes.
Yes, precisely. In older versions of Rust, each lifetime always lasted until the end of a lexical scope (e.g., a closing brace), which matched your expectation. But Rust 1.31 introduced a new feature called non-lexical lifetimes. Now, borrows can end immediately after their last use.
Note that the ability of the compiler to (soundly) end the lifetime of the &mut Mog before it goes out of scope is related to &mut Mog doing nothing when dropped. In Rust, destructors always run at the end of scope, so in general on would think that might be problematic:
fn main() {
let mut mog = Mog { name: "Joe".to_string() };
let another = &mut mog;
another.name = "Kim".to_string();
mog.name = "Back Again".to_string();
// in principal, destructor of `another` runs here; but actually,
// explicitly calling `drop(another)` here makes the code no longer compile
}
And in fact, if you wrap the reference into another struct, e.g.
well… that still works… but if this new struct were to implement Drop it stops working
#[derive(Debug)]
struct Mog {
pub name: String
}
struct ContainsReference<'a>(&'a mut Mog);
impl Drop for ContainsReference<'_> { fn drop(&mut self) {} }
fn main() {
let mut mog = Mog { name: "Joe".to_string() };
let another = ContainsReference(&mut mog);
another.0.name = "Kim".to_string();
mog.name = "Back Again".to_string();
}
error[E0506]: cannot assign to `mog.name` because it is borrowed
--> src/main.rs:13:5
|
11 | let another = ContainsReference(&mut mog);
| -------- borrow of `mog.name` occurs here
12 | another.0.name = "Kim".to_string();
13 | mog.name = "Back Again".to_string();
| ^^^^^^^^ assignment to borrowed `mog.name` occurs here
14 | }
| - borrow might be used here, when `another` is dropped and runs the `Drop` code for type `ContainsReference`
For more information about this error, try `rustc --explain E0506`.
There’s dedicated logic in the compiler that checks the interaction of destructors and lifetimes.