Ownership and Lifetimes
Ownership is the breakout feature of Rust. It allows Rust to be completelymemory-safe and efficient, while avoiding garbage collection. Before gettinginto the ownership system in detail, we will consider the motivation of thisdesign.
We will assume that you accept that garbage collection (GC) is not always anoptimal solution, and that it is desirable to manually manage memory in somecontexts. If you do not accept this, might I interest you in a differentlanguage?
Regardless of your feelings on GC, it is pretty clearly a massive boon tomaking code safe. You never have to worry about things going away too soon(although whether you still wanted to be pointing at that thing is a differentissue…). This is a pervasive problem that C and C++ programs need to dealwith. Consider this simple mistake that all of us who have used a non-GC’dlanguage have made at one point:
fn as_str(data: &u32) -> &str {
// compute the string
let s = format!("{}", data);
// OH NO! We returned a reference to something that
// exists only in this function!
// Dangling pointer! Use after free! Alas!
// (this does not compile in Rust)
&s
}
This is exactly what Rust’s ownership system was built to solve.Rust knows the scope in which the &s
lives, and as such can prevent it fromescaping. However this is a simple case that even a C compiler could plausiblycatch. Things get more complicated as code gets bigger and pointers get fed throughvarious functions. Eventually, a C compiler will fall down and won’t be able toperform sufficient escape analysis to prove your code unsound. It will consequentlybe forced to accept your program on the assumption that it is correct.
This will never happen to Rust. It’s up to the programmer to prove to thecompiler that everything is sound.
Of course, Rust’s story around ownership is much more complicated than justverifying that references don’t escape the scope of their referent. That’sbecause ensuring pointers are always valid is much more complicated than this.For instance in this code,
let mut data = vec![1, 2, 3];
// get an internal reference
let x = &data[0];
// OH NO! `push` causes the backing storage of `data` to be reallocated.
// Dangling pointer! Use after free! Alas!
// (this does not compile in Rust)
data.push(4);
println!("{}", x);
naive scope analysis would be insufficient to prevent this bug, because data
does in fact live as long as we needed. However it was changed while we hada reference into it. This is why Rust requires any references to freeze thereferent and its owners.