DEV Community

Cover image for 🦀 Rust Master Class - Chapter 20: Advanced Lifetimes
Oludayo Adeoye
Oludayo Adeoye

Posted on

🦀 Rust Master Class - Chapter 20: Advanced Lifetimes

🦀 Rust Master Class - Chapter 20: Advanced Lifetimes


Visiting my grandfather's grave. The time we had wasn't enough, but it was what we had. Lifetimes in Rust aren't about duration — they're about knowing what outlives what.


Advanced lifetimes in Rust extend beyond basic function signatures to handle complex reference relationships, subtyping, and memory safety constraints. Based on the sources, the key concepts include the outlives relationship, lifetime bounds, variance, and anonymous lifetimes.

1. The Outlives Relationship

The foundational concept in advanced lifetimes is the outlives relationship. If one lifetime 'b lasts at least as long as another lifetime 'a, it is written as 'b: 'a . This relationship is essential for functions that take multiple references and need to guarantee that one piece of data does not expire before another that depends on it .

2. Lifetime Bounds

Lifetime bounds can be applied to generic types. The bound T: 'a means that the type T (and any references it contains) must last at least as long as the lifetime 'a .

  • Implied Bounds: In many cases, Rust assumes these bounds. For example, in a struct GenericWithLifetime<'a, T>, if it contains a field &'a T, the bound T: 'a is implied because the reference would be invalid if T didn't last at least as long as 'a .

Code Example:

struct GenericWithLifetime<'a, T> { 
    b: &'a T, // Implied bound: T must outlive 'a
}

// Explicit bound requirement
fn requires_lifetime_bound<'a, T: 'a>() {} 
Enter fullscreen mode Exit fullscreen mode

[Source: 120, 121]

3. Subtyping and Variance

Variance describes how the subtyping relationship between lifetimes (e.g., 'static outliving 'a) translates to the subtyping relationship between complex types .

  • Covariance: If 'x: 'y, then T<'x> is a subtype of T<'y> .
    • Example: Because 'static outlives 'a, an immutable reference &'static str is a subtype of &'a str and can be passed to functions expecting the shorter lifetime .
  • Contravariance: If 'x: 'y, then T<'y> is a subtype of T<'x> .
    • Example: This primarily occurs in function arguments. A function type fn(&'a str) is a subtype of fn(&'static str) because a function that can handle a shorter lifetime can safely handle a longer one .
  • Invariance: If 'x: 'y, then T<'x> has no relationship with T<'y> .
    • Example: Mutable references (&mut T) are invariant. If they were covariant, you could accidentally store a short-lived reference into a long-lived container, causing a dangling pointer .

Code Example (Invariance Error):

// This function expects a mutable reference to a vector of references
fn accept_vec(str_vec: &mut Vec<&str>, s: &str) { 
    str_vec.push(s) 
} 

fn main() { 
    // Create a mutable variable
    let mut vec: Vec<&'static str> = vec!["", ""]; 
    // Create a new variable
    let non_static_str = &*String::from(""); 
    // This would fail if &mut was not invariant:
    // accept_vec(&mut vec, non_static_str); 
}
Enter fullscreen mode Exit fullscreen mode

[Source: 124, 129]

4. Anonymous Lifetimes ('_)

Anonymous lifetimes are used when you want the compiler to infer (guess) the lifetime . This is allowed only when there is exactly one logical choice for the compiler to make .

  • Common Use Case: Inside an impl block, using &'_ str tells the compiler the lifetime must match the lifetime of self .

Code Example:

impl MyStruct {
    // The compiler guesses this matches the lifetime of 'self'
    fn get_ref(&self) -> &'_ str {
        "example"
    }
}
Enter fullscreen mode Exit fullscreen mode

[Source: 125]

5. Lifetime Elision Rules

Advanced Rust relies on three rules to elide (omit) lifetimes in function signatures automatically:

  1. Each reference parameter gets its own unique lifetime parameter .
  2. If there is exactly one input lifetime, that lifetime is assigned to all output references .
  3. If there are multiple input lifetimes but one is &self or &mut self, the lifetime of self is assigned to all output references .

If a function does not satisfy these rules, lifetimes must be specified explicitly .


📖 Download the full PDF: https://drive.google.com/file/d/1GQGz8ztwPG4iqAgTDQ11Iw-YJXeKX6W2/view?usp=sharing

Part 20 of the Rust Master Class series — STEM EdTech | Automation Consulting | Rust Tutoring

RustLang #Programming #LearnToCode #STEM #EdTech


📚 Practice Resources

GitHub Repository: https://github.com/PacktPublishing/Rust-Programming-Master-Class-from-Beginner-to-Expert

Try it yourself: https://play.rust-lang.org/

Run the code from this chapter in the Rust playground, then clone the repo to continue your Rust journey!


Part 20 of the Rust Master Class series — STEM EdTech | Automation Consulting | Rust Tutoring

RustLang #Programming #LearnToCode #STEM #EdTech

Top comments (0)