Lifetimes
A lifetime is the time between when a value is allocated and when it's deallocated.
Here's an example:
fn jazz_releases(years: &[i64]) -> Releases {
let eighties: &[i64] = &years[0..2];
let nineties: &[i64] = &years[2..4];
Releases {
years,
eighties,
nineties,
}
}
fn main() {
let releases = {
let all_years: Vec<i64> =
// alloc
vec![
1980, 1985, 1990, 1995, 2000, 2000
];
jazz_releases(all_years)
}; // dealloc
for year in releases.eighties.iter() {
println!("Eighties year: {}", year);
}
}
Here the lifetime of all_years
begins at the // alloc
comment, when vec!
gets called, and ends at the // dealloc
comment, when all_years
goes
out of scope.
One of the rules Rust's borrow checker enforces is that you can never reference a value after its lifetime has ended. (If you did, it would be a use-after-free bug.)
Sometimes these borrow checker errors can come as a surprise. For example, in
the code above, the call to println!
references releases.eighties
, but
releses.eighties
is a slice of all_years
elements - and all_years
has
already been deallocated by the time the println!
call is reached.
Another way of saying this is that the lifetime of releases
is tied to
the lifetime of all_years
. When the lifetime of all_years
ends, the lifetime
of releases
also ends. In this case, that means the lifetime of releases
is never valid, because all_years
goes out of scope before releases
even
receives its value! That's what leads to the borrow checker error.
The more references a program has, the trickier it can get to follow which references' lifetimes depend on which others. Fortunately, Rust has a way to keep track of them explicitly.