loading...

Some examples of Rust Lifetimes in a struct

frankmeza profile image Frank Meza ・5 min read

So, you've decided to deep dive into Rust lifetime parameters. Nice!

Let's talk about when dinosaurs roamed the earth.

SIMPLE CASE, COMPILER APPROVED ✅

this simple snippet in a Rust sandbox

#[derive(Debug)]
struct Earth {
    location: String,
}

#[derive(Debug)]
struct Period {
    age: String,
}

#[derive(Debug)]
struct Dinosaur {
    location: Earth,
    period: Period,
    name: String,
}

fn main() {
    let montana = Earth {
        location: String::from("Montana"),
    };

    let jurassic = Period {
        age: String::from("Jurassic"),
    };

    let t_rex = Dinosaur {
        name: String::from("Tyrannosaurus Rex"),
        location: montana,
        period: jurassic,
    };

    println!("{:?}", t_rex.name);
    println!("{:?}", t_rex.location);
    println!("{:?}", t_rex.period);

    // compiler disallows these next lines, why?
    // println!("{:?}", montana); 
    // println!("{:?}", jurassic); 
}

Our aim here is to create a Rust compiler-approved Dinosaur struct, incorporating Earth and Period structs. This is what we have after a first pass of putting some props into a few struct objects.

At the bottom of the snippet is a commented-out line that the compiler does not allow. Why? Because montana and jurassic are now owned by t_rex and access to their values is now only allowed via t_rex. This is not how we want the code to behave.

Some more change is left to be done with the code, but this is the basic idea. We want a dinosaur to have knowledge of its location and its time period... but definitely not for the struct itself to own the Earth and Period structs.

SLIGHTLY LESS NAÏVE APPROACH, NOT COMPILER-FRIENDLY YET ❌

Now let's say you decide to start experimenting with Rust references and come up with this idea:

struct Dinosaur {
    location: &Earth,
    period: &Period,
    name: String,
}

But even before running any business logic between the structs, the compiler already wants to know more, it wants explicit lifetime parameters on Dinosaur, so you dig around and put this together:

LIFETIME PARAMETER INFORMED CASE, COMPILER APPROVED ✅

this lifetime snippet in a Rust sandbox

#[derive(Debug)]
struct Earth {
    location: String,
}

#[derive(Debug)]
struct Period {
    age: String,
}

// instead of receiving the values `location` and `period`,
// Dinosaur borrows (pre-existing) instances of Earth, Period
#[derive(Debug)]
struct Dinosaur<'a> {
    location: &'a Earth,
    period: &'a Period,
    name: String,
}

fn main() {
    let montana = Earth {
        location: String::from("Montana"),
    };

    let jurassic = Period {
       age: String::from("Jurassic"),
    };

    let t_rex = Dinosaur {
        name: String::from("Tyrannosaurus Rex"),
        location: &montana,
        period: &jurassic,
    };

    println!("{:?}", t_rex.name);

    println!("ACCESSING MONTANA DIRECTLY {:?}", montana); 
    println!("ACCESSING JURASSIC DIRECTLY {:?}", jurassic); 
}

In a nutshell:

  • Dinosaur is receiving location and period as borrowed references.
  • Since Dinosaur does not own the values, the Rust compiler wants explicit confirmation that the original value that the references refer to will still be valid as long as Dinosaur needs them.
  • This explicit confirmation comes in the form of lifetime parameters, on Dinosaur<&'a>.

HANDLING SEVERAL LIFETIMES IN A SINGLE STRUCT

"But why doesn't each prop in Dinosaur gets its own letter? What about this <'a, 'b> that I saw somewhere on Stack Overflow? How does that work?"

Oh, you mean something like Dinosaur<'a, 'b> , right? So each of the letters represents a different scope that the struct knows about. Here is a link to the official documentation, but the important part of it right now is the following snippet, illustrating what the lifetime parameters signify in terms of scopes.

{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}                         // ---------+

I'll leave integrating this stuff into our Dinosaur example up to you, o dear reader of mine.

Discussion

markdown guide
 

I think it's important to point out that T-rex actually lived in the Cretaceous period ;)