Newer Rust developers are often taught to think of lifetimes as corresponding to scopes
That’s often correct, and usually useful, but the reality is a little more complex.
A lifetime is really a name for a region of code that some reference must be valid for.
While a lifetime will frequently coincide with a scope, it does not have to
At the heart of Rust lifetimes is the borrow checker.
use rand::Rng;
let mut x = Box::new(42.0);
let r = &x; // 1 Lifetime starts = 'a
if rand::thread_rng().gen_bool(0.5) {
*x = 84.0;
// 2 no conflicting borrows found, r is never used
} else {
// *x = 84.0; this will fail, already borrowed below
println!("{}", r); // 3 // 'a
}
//4 lifetime ends
Lifetimes can get quite convoluted.
you can see an example of a lifetime that has holes, where it’s intermittently invalid between where it starts and where it ultimately ends.
let mut x = Box::new(42);
let mut z = &x; // 'a -> 1 lifetime starts
for i in 0..100 {
println!("{}", z); // 'a -> 2
//3 move out of x, which ends the lifetime 'a
x = Box::new(i);
z = &x; // 'a -> 4 restart the lifetime
}
println!("{}", z); // 'a
Listing 1-9, “holes in lifetime”
When we reassign z
later, we are creating an entirely new variable that exists only from that point forward.
It just so happens that that new variable is also named z
.