avatarAnkit Tanna

Summary

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

5668

Abstract

nd create two more references out of it, <code>eighties</code> and <code>nineties</code>. We return the <code>Releases</code> struct from the function. Inside of the <code>main</code> function, we create an anonymous scope <code>{}</code> to evaluate <code>releases</code> variable which will hold the value returned from the <code>jazz_releases</code> function. Outside the anonymous scope <code>{}</code>, after the <code>releases</code> is evaluated, we print the length of <code>eighties</code> and <code>nineties</code> from the <code>releases</code> variable. The problem is that the lifetime of <code>all_years</code> is over before the print macro executes and hence we get a compiler error or use after free error.</p><figure id="41f6"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*5VcQNYiPlZP-QGzlKCMzUQ.png"><figcaption><b>Lifetime error example</b></figcaption></figure><p id="f2fd">Lifetime of the <code>all_years</code> ends as soon as the anonymous scope ends. Because <code>all_years</code> 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 <code>all_years</code> 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.</p><h2 id="1017">So why do we get use after free error?</h2><p id="a514">So all the memory is allocated at the line where we assign <code>all_years</code> with a vector of years. It gets allocated as soon as an assignment is made to the variable <code>all_years</code> and it get’s de-allocated as soon as the anonymous scope is finished.</p><p id="3ecf">Let’s go one line above the anonymous scope ending. Where <code>jazz_releases</code> method is called. Notice that we are passing a reference to <code>all_years </code>using <code>&all_years</code> which means, it is not a copy of <code>all_years</code> 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 <b>use after free </b>error. All our child references, <code>&all_years</code>, and the references used to create <code>eighties</code> and <code>nineties</code> are now pointing to a free space. Refer to the below diagram:</p><figure id="4804"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*IKFje8LMJ9VDs3IbwkNtTg.png"><figcaption></figcaption></figure><p id="6035">The highlighted code shows the <b>lifetime </b>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.</p><p id="45bc">The lines in our <code>main</code> function:</p><div id="814a"><pre><span class="hljs-keyword">let</span> <span class="hljs-variable">eighties</span> = releases.eighties; <span class="hljs-keyword">let</span> <span class="hljs-variable">nineties</span> = releases.nineties;</pre></div><p id="3618">are referring to <code>all_years</code> after its lifetime has ended. This is why you get the compiler error.</p><h2 id="29e7">What can be done to tackle this lifetime error?</h2><p id="14c7">The parameter of the function <code>jazz_releases</code> is still within the lifetime of the <code>all_years</code> vector. See the highlighted code to understand what do we mean:</p><figure id="160e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*xlCGDmCQo6h7LXkQafWZHA.png"><figcaption>Highlighted code indicating that years is still in the lifetime of <code>all_years</code></figcaption></figure><p id="d545">While this function is executing, we are still in the below highlighted area of the code:</p><figure id="818e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*tHy-b9MFc3cqdpuz2UzUUw.png"><figcaption>years is still in the lifetime of the all_years whose lifetime spans the highlighted code</figcaption></figure><p id="c8a6">The problem comes into picture when <code>Releases</code> is returned from the function. Releases does have some references to <code>all_years</code> in the form of slices and it stores them in <code>years</code>, <code>eighties</code> and <code>nineties</code>.</p><h2 id="efa6">Lifetime Annotations:</h2><p id="6dc9">Lifetime annotations can be written as <code>'a</code> and colloqually spelled as <code>tick a</code>.</p><div id="fdf5"><pre><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Releases</span><<span class="hljs-symbol">'y</span>> { years: &<span class="hljs-symbol">'y</span> [<span class="hljs-type">i64</span>], eighties: &<span class="hljs-symbol">'y</span> [<span class="hljs-type">i64</span>], nineties: &<span class="hljs-symbol">'y</span> [<span class="hljs-type">i64</span>], }

<span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() { <span class="hljs-keyword">let</span> <span class="hljs-variable">years</span>: <span class="hljs-type">Vec</span><<span class="hljs-type">i64</span>> = <span class="hljs-built_in">vec!</span>[<span class="hljs-number">1988</span>, <span class="hljs-number">1989</span>, <span class="hljs-

Options

number">1990</span>, <span class="hljs-number">1991</span>, <span class="hljs-number">2004</span>, <span class="hljs-number">2006</span>];

<span class="hljs-keyword">let</span> <span class="hljs-variable">releases</span> = {
    <span class="hljs-keyword">let</span> <span class="hljs-variable">all_years</span>: <span class="hljs-type">Vec</span>&lt;<span class="hljs-type">i64</span>&gt; = <span class="hljs-built_in">vec!</span>[<span class="hljs-number">1988</span>, <span class="hljs-number">1989</span>, <span class="hljs-number">1990</span>, <span class="hljs-number">1991</span>, <span class="hljs-number">2004</span>, <span class="hljs-number">2006</span>];
    <span class="hljs-title function_ invoke__">jazz_releases</span>(&amp;all_years);
};

<span class="hljs-keyword">let</span> <span class="hljs-variable">eighties</span> = releases.eighties;
<span class="hljs-keyword">let</span> <span class="hljs-variable">nineties</span> = releases.nineties;

<span class="hljs-built_in">println!</span>(<span class="hljs-string">"eighties: {}"</span>, eighties.<span class="hljs-title function_ invoke__">len</span>());
<span class="hljs-built_in">println!</span>(<span class="hljs-string">"nineties: {}"</span>, nineties.<span class="hljs-title function_ invoke__">len</span>());

}

<span class="hljs-keyword">fn</span> <span class="hljs-title function_">jazz_releases</span><<span class="hljs-symbol">'a</span>>(years: &<span class="hljs-symbol">'a</span> [<span class="hljs-type">i64</span>]) <span class="hljs-punctuation">-></span> Releases<<span class="hljs-symbol">'a</span>> { <span class="hljs-keyword">let</span> <span class="hljs-variable">eighties</span>: &<span class="hljs-symbol">'a</span> [<span class="hljs-type">i64</span>] = &years[<span class="hljs-number">0</span>..<span class="hljs-number">2</span>]; <span class="hljs-keyword">let</span> <span class="hljs-variable">nineties</span>: &<span class="hljs-symbol">'a</span> [<span class="hljs-type">i64</span>] = &years[<span class="hljs-number">2</span>..<span class="hljs-number">4</span>];

Releases {
    years: years,
    eighties: eighties,
    nineties: nineties,
}

}</pre></div><p id="1d5e">We create a lifetime parameter named <code>'a</code> and pass it on to the function and it’s references using the angled bracket <code><></code> 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.</p><p id="c51a">The code you provided will not compile as-is because of a borrowing issue in Rust. The problem is with the <code>releases</code> variable. We are trying to assign the result of the <code>jazz_releases</code> function call to <code>releases</code>, but that result is a temporary value, and you can't have references to temporary values outside of their scope.</p><h2 id="a416">Lifetime Elision:</h2><p id="8b93">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 <code><'_></code> 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.</p><p id="379e">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.</p><p id="8545">Lifetimes are the reason where you may encounter some of the most obscure borrow checker errors which may be difficult to figure out.</p><p id="a24b">The colloquial <code>tick a</code> or <code>tick b</code> are extremely common and giving a descriptive names for the lifetimes are not that common. It’s pretty common to have single letter references.</p><h2 id="7831">Static Lifetimes:</h2><p id="0163">Static lifetimes come into picture when you are assigning string to a variable. For e.g. <code>let name = "Sam";</code> is something that will be translated to <code>let name: &'static str = "Sam";</code>. 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 <code>let name: &str = “Sam”;</code>.</p><p id="26c2">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.</p><p id="2dff">I hope you enjoyed this article about Rust Lifetime. I really hope you enjoyed this entire series of <b>The Rust Programming Language. </b>Please leave your feedback in the comments section.</p><p id="7ee7">You can subscribe to my newsletter about <b>The Rust Programming Language <a href="https://tinyletter.com/ankittanna"></a></b><a href="https://tinyletter.com/ankittanna">here</a>. You can read about all the articles in this series <a href="https://readmedium.com/the-rust-programming-language-4b22bc717ecc">here</a>.</p></article></body>

The Rust Programming Language

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:

Lifetime of a variable

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 error example

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:

Highlighted code indicating that years is still in the lifetime of all_years

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

years is still in the lifetime of the all_years whose lifetime spans the highlighted 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.

Rust
Rust Programming Language
Web Development
Performance
Memory Improvement
Recommended from ReadMedium