Lifetimes

Lifetimes in Methods

When implementing methods on a struct or enum that declares lifetime parameters (impl<'a> Type<'a>), the lifetime parameters must be declared after impl and passed to the type name. Inside method signatures, the compiler's third lifetime elision rule often makes explicit lifetime annotations on parameters and return values unnecessary.

Declaring impl<'a> Struct<'a>

The lifetime 'a is part of the struct's full type signature. Therefore, an impl block must introduce 'a in impl<'a> so it is in scope for the target type Excerpt<'a>:

struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    // Associated function (constructor): ties input `'a` to `Excerpt<'a>`
    fn new(part: &'a str) -> Self {
        Self { part }
    }

    // Method returning a value that does not borrow anything
    fn level(&self) -> i32 {
        3
    }
}

fn main() {
    let text = String::from("Rust memory safety without garbage collection.");
    let excerpt = Excerpt::new(&text[..18]);
    println!("Excerpt '{}' has level {}", excerpt.part, excerpt.level());
}
Excerpt 'Rust memory safety' has level 3

Method Lifetime Elision (&self Rule)

The three lifetime elision rules apply to method signatures:

  1. Each reference parameter (&self, &str, etc.) gets its own distinct lifetime parameter.
  2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output reference parameters.
  3. Method rule: If one of the input parameters is &self or &mut self, the lifetime of self is assigned to all elided output reference lifetimes.
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    // Rule 1 assigns `'s` to `&self` and `'b` to `announcement`.
    // Rule 3 assigns `'s` (the lifetime of `&self`) to the return type `&str`.
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {announcement}");
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael.");
    let excerpt = Excerpt { part: &novel[..7] };

    let part = excerpt.announce_and_return_part("New chapter starting");
    println!("Returned part: {part}");
}
Attention please: New chapter starting
Returned part: Call me

Returning 'a vs. Returning the Lifetime of &self

Notice a subtle distinction on a view struct Excerpt<'a>:

  • Returning &str with elision (fn get(&self) -> &str) ties the returned slice to the borrow of &self (&'s self -> &'s str).
  • Returning &'a str explicitly (fn as_str(&self) -> &'a str) ties the returned slice to the underlying buffer 'a, allowing the returned &'a str to live even after the Excerpt<'a> struct itself is dropped!
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    // Explicitly returns `'a` (the underlying string slice's lifetime),
    // not the shorter borrow of `&self`:
    fn into_underlying_str(&self) -> &'a str {
        self.part
    }
}

fn main() {
    let backing = String::from("Long-lived backing text");
    let slice_from_method: &str;

    {
        let temp_excerpt = Excerpt { part: &backing[..10] };
        slice_from_method = temp_excerpt.into_underlying_str();
    } // `temp_excerpt` is dropped here, but `slice_from_method` borrows `backing` ('a)!

    println!("Still valid after struct drop: '{slice_from_method}'");
}
Still valid after struct drop: 'Long-lived'

Using the Anonymous Lifetime '_ in impl Blocks

When a method does not need to name the struct's lifetime parameter 'a inside its body or return type, Rust allows eliding it in the impl header using the anonymous lifetime '_:

struct Excerpt<'a> {
    part: &'a str,
}

impl Excerpt<'_> {
    fn word_count(&self) -> usize {
        self.part.split_whitespace().count()
    }
}

fn main() {
    let e = Excerpt { part: "one two three" };
    println!("Words: {}", e.word_count());
}
Words: 3