The Rust Programming Language — Lifetime — Lifetime, Lifetime Annotations, Lifetime Elision and Static Lifetime
Diving very deep into Lifetimes! Understand how Rust’s Memory Management System works.
Lifetime:
A lifetime is the period starting from when a variable is allocated and de-allocated from the memory. Refer the below diagram:

This is the last topic in this series which emphasises on how Rust’s Memory Management System works. We’ll start with below code snippet:
struct Releases {
years: &[i64],
eighties: &[i64],
nineties: &[i64],
}
fn main() {
let years: Vec<i64> = vec![1988, 1989, 1990, 1991, 2004, 2006];
let eighties: &[i64] = &years[0..2];
let nineties: &[i64] = &years[2..4];
}The code snippet is fairly simple. We have a years vector and eighties and nineties references from the years vector. We also have a struct Releases which has years, eighties, and nineties as the members of the struct which are also nothing but the reference of the memory where years is stored in the heap.
I am creating a function which will return this Releases struct. Refer the below code snippet.
struct Releases {
years: &[i64],
eighties: &[i64],
nineties: &[i64],
}
fn main() {
let years: Vec<i64> = vec![1988, 1989, 1990, 1991, 2004, 2006];
let releases = {
let all_years: Vec<i64> = vec![1988, 1989, 1990, 1991, 2004, 2006];
jazz_releases(&all_years);
} // dealloc(all_years)
let eighties = releases.eighties;
let nineties = releases.nineties;
println!("eighties: {}", eighties.len());
println!("nineties: {}", nineties.len());
}
fn jazz_releases(years: &[i64]) -> Releases {
let eighties: &[i64] = &years[0..2];
let nineties: &[i64] = &years[2..4];
Releases {
years: years,
eighties: eighties,
nineties: nineties,
}
}We have created a new function called jazz_releases and we get the references of the years passed in and create two more references out of it, eighties and nineties. We return the Releases struct from the function. Inside of the main function, we create an anonymous scope {} to evaluate releases variable which will hold the value returned from the jazz_releases function. Outside the anonymous scope {}, after the releases is evaluated, we print the length of eighties and nineties from the releases variable. The problem is that the lifetime of all_years is over before the print macro executes and hence we get a compiler error or use after free error.

Lifetime of the all_years ends as soon as the anonymous scope ends. Because all_years goes out of scope and we de-allocate it from the memory, all the references that are generated from the heap of the vector are also no longer valid as they have originated from the same memory space in the heap where all_years was located. Looking at the code, it does not look obvious at all to us. So let’s see how Rust compiler saves us. We’ll also see how the concept of lifetime is helping us understand and build a mental model in our code of what’s actually happening.
So why do we get use after free error?
So all the memory is allocated at the line where we assign all_years with a vector of years. It gets allocated as soon as an assignment is made to the variable all_years and it get’s de-allocated as soon as the anonymous scope is finished.
Let’s go one line above the anonymous scope ending. Where jazz_releases method is called. Notice that we are passing a reference to all_years using &all_years which means, it is not a copy of all_years but just referencing the same memory in the heap. This memory in the heap got de-allocated after the scope finished and hence the references that have been generated referencing the heap memory are also not usable as that memory is now free to be used by any other thread of the same or different program. Hence you get use after free error. All our child references, &all_years, and the references used to create eighties and nineties are now pointing to a free space. Refer to the below diagram:

The highlighted code shows the lifetime of the vector and references originating from it. As soon as the lifetime is over, the child references are not useful for us. The highlighted code shows the time between the vector getting allocated a memory in the heap and de-allocated memory from the heap.
The lines in our main function:
let eighties = releases.eighties;
let nineties = releases.nineties;are referring to all_years after its lifetime has ended. This is why you get the compiler error.
What can be done to tackle this lifetime error?
The parameter of the function jazz_releases is still within the lifetime of the all_years vector. See the highlighted code to understand what do we mean:

all_yearsWhile this function is executing, we are still in the below highlighted area of the code:

The problem comes into picture when Releases is returned from the function. Releases does have some references to all_years in the form of slices and it stores them in years, eighties and nineties.
Lifetime Annotations:
Lifetime annotations can be written as 'a and colloqually spelled as tick a.
struct Releases<'y> {
years: &'y [i64],
eighties: &'y [i64],
nineties: &'y [i64],
}
fn main() {
let years: Vec<i64> = vec![1988, 1989, 1990, 1991, 2004, 2006];
let releases = {
let all_years: Vec<i64> = vec![1988, 1989, 1990, 1991, 2004, 2006];
jazz_releases(&all_years);
};
let eighties = releases.eighties;
let nineties = releases.nineties;
println!("eighties: {}", eighties.len());
println!("nineties: {}", nineties.len());
}
fn jazz_releases<'a>(years: &'a [i64]) -> Releases<'a> {
let eighties: &'a [i64] = &years[0..2];
let nineties: &'a [i64] = &years[2..4];
Releases {
years: years,
eighties: eighties,
nineties: nineties,
}
}We create a lifetime parameter named 'a and pass it on to the function and it’s references using the angled bracket <> just like we passed typed parameters to the structs. What we are essentially doing is linking the lifetime of all the references to a particular name. This means that all the references with a particular lifetime parameter name are going to live the same amount of time. Using these variables, we now have an actual way of expressing that there is a relation between lifetimes. They give a clear indication to us that we are using something that is outside it’s lifetime.
The code you provided will not compile as-is because of a borrowing issue in Rust. The problem is with the releases variable. We are trying to assign the result of the jazz_releases function call to releases, but that result is a temporary value, and you can't have references to temporary values outside of their scope.
Lifetime Elision:
Lifetime Elision is a way of compiler telling that I know the lifetime of the variable so we need not explicitly mention it. It is represented or equivalent of <'_> and it’s often redundant so many times we don’t even bother writing it. It gives the responsibility back to the compiler to compile the code and take case of the unknown lifetimes. Rust compiler is pretty smart enough to figure out about most of the methods with single references as a parameter.
The compiler does not explicitly control the references lifetime and extend them even beyond what they should live. The underlying fundamental principle is that references are not supposed to outlive the memory they are referencing.
Lifetimes are the reason where you may encounter some of the most obscure borrow checker errors which may be difficult to figure out.
The colloquial tick a or tick b are extremely common and giving a descriptive names for the lifetimes are not that common. It’s pretty common to have single letter references.
Static Lifetimes:
Static lifetimes come into picture when you are assigning string to a variable. For e.g. let name = "Sam"; is something that will be translated to let name: &'static str = "Sam";. These do not get allocated and do not get de-allocated. They stay permanently in the binary. It does not make a stack/heap allocation. So they keep it in the memory and it will be referenced from the memory in the binary. Usually we write it like let name: &str = “Sam”;.
We will see such code in the type annotations of the packages we use. We also get an idea of how do we read such code.
I hope you enjoyed this article about Rust Lifetime. I really hope you enjoyed this entire series of The Rust Programming Language. Please leave your feedback in the comments section.
You can subscribe to my newsletter about The Rust Programming Language here. You can read about all the articles in this series here.






