It's not just a matter of memory use. References and mutable references form a sort of compile-time read-write mutex; you can't take a mutable reference without first dropping all other references. See https://stackoverflow.com/questions/50251487/what-are-non-le... for more.
NLL only affects values without destructors.
Now actually this is a quite nasty object with implicit global side effects which you should avoid in the first place, but for the mutex case i don't know of a better option, maybe Rust has a better way to handle this?
fn main() {
let mut a = 1;
let b = &mut a;
*b = 2;
println!("{}", a); // prints "2"
// *b = 4; // If this line is uncommented, compile time error.
}
Because b is a mutable reference to a, this means that a cannot be accessed directly until b goes out of scope. In this sense, b goes out of scope the last time it's used. _However_, AFAIK, b isn't actually de-allocated until the end of the function.Of course, it doesn't matter in this trivial case, because b is just some bytes in the current stack frame so there's nothing to actually de-allocate. But if b were a complex type that _also_ had some memory to de-allocate, this wouldn't happen until the end of main(). But in this case, b's scope also lasts until the end of main, which is kind of like adding that last line back in...
This can be seen in the following example, where b has an explicit type:
struct B<'a>(&'a mut i32);
impl<'a> Drop for B<'a> {
fn drop(&mut self) {
// We'd still have a mutable reference to a here...
// If B owned resources and needed to free them, this is where that would happen
}
}
fn main() {
let mut a = 1;
let b = B(&mut a);
*b.0 = 2;
std::mem::drop(b); // Comment this line out, get compiler error
println!("{}", a); // prints "2"
}
In this example, without the std::mem::drop() line, the implementation for Drop (i.e., B's destructor), B::drop would be implicitly called at the end of the function. But in that case, B::drop() would still have a mutable reference to a, which makes the println call produce a "cannot borrow `a` as immutable because it is also borrowed as mutable" compile time error.In other words, this "going out of scope at last use" is really about rust's lifetimes system, not memory allocation.
IMHO... this is one of the rough edges in rust's somewhat steep learning curve. Rust's lifetimes rules make the language kind of complicated, though getting memory safety in a systems programming language is worth the trade-off. There's a lot of syntactic sugar that makes things a LOT easier and less verbose in most cases, but the learning curve trade-off for _that_ is that, when you _do_ run into the more complex cases that the compiler can't figure out for you, it's easy to get lost, because there are a few extra puzzle pieces to fit together. Still way better than the foot-gun that is C, though. At least for me... YMMV, obviously.
In which case there’s only one situation where I could see this useful, and that’s when you are building a large object to replace an old one.
The semantics of
In most languages is that foo exists until reassigned. When the object represents a nontrivial amount of memory, and you don’t have fallback behavior that keeps the old data, then you might see something like Most of the rest of the time it’s not worth the hassle.