Lifetimes

Lifetime Elision Rules

Lifetime elision is a set of deterministic patterns built into rustc that allow you to omit explicit 'a lifetime annotations on common function and method signatures. When a signature matches the elision rules, the compiler fills in the lifetime parameters automatically behind the scenes.

Input Lifetimes vs Output Lifetimes

  • Input lifetimes: Lifetimes on function or method parameters (fn f(x: &'a str)).
  • Output lifetimes: Lifetimes on the return type (-> &'a str).

The Three Lifetime Elision Rules

rustc applies three sequential rules to deduce missing lifetimes in a function signature:

Rule Applies To What the Compiler Does
Rule 1 Input lifetimes Every reference parameter that omits a lifetime gets its own distinct lifetime parameter ('a, 'b, 'c, ...)
Rule 2 Output lifetimes (Single input) If there is exactly one input lifetime parameter ('a), that lifetime 'a is assigned to all elided output references
Rule 3 Output lifetimes (Methods with &self / &mut self) If there are multiple input lifetimes, but one of them is &self or &mut self, the lifetime of self is assigned to all elided output references

If any output reference still lacks a lifetime after applying Rules 1, 2, and 3, the compiler stops and reports error[E0106]: missing lifetime specifier instead of guessing.

Examples of How `rustc` Applies the 3 Elision Rules:

1. You write:      fn trim_left(s: &str) -> &str
   Rule 1 (in):    fn trim_left<'a>(s: &'a str) -> &str
   Rule 2 (1 in):  fn trim_left<'a>(s: &'a str) -> &'a str   (✅ Compiles without annotations!)

2. You write:      fn get_name(&self, default: &str) -> &str
   Rule 1 (in):    fn get_name<'a, 'b>(&'a self, default: &'b str) -> &str
   Rule 3 (self):  fn get_name<'a, 'b>(&'a self, default: &'b str) -> &'a str (✅ Compiles!)

3. You write:      fn choose(a: &str, b: &str) -> &str
   Rule 1 (in):    fn choose<'a, 'b>(a: &'a str, b: &'b str) -> &str
   Rules 2 & 3:    (2 inputs, no `self` -> ❌ E0106: programmer must annotate `<'a>`!)
struct Document {
    title: String,
    body: String,
}

impl Document {
    // Rule 3 applies: `&self` lifetime is automatically assigned to the returned `&str`!
    fn title_or_check(&self, _prefix: &str) -> &str {
        &self.title
    }
}

// Rule 2 applies: single input reference `&str` is assigned to both tuple outputs!
fn split_header(line: &str) -> (&str, &str) {
    line.split_once(':').unwrap_or((line, ""))
}

fn main() {
    let doc = Document {
        title: String::from("Rust 2024"),
        body: String::from("Content"),
    };

    println!("Title:  {} (len {})", doc.title_or_check("tag"), doc.body.len());
    println!("Header: {:?}", split_header("Host: localhost"));
}
Title:  Rust 2024 (len 7)
Header: ("Host", " localhost")

Overriding Rule 3 in Methods When Returning From Another Parameter

Because Rule 3 assumes a method returning a reference borrows from &self, if a method actually returns a reference borrowed from a different parameter other, you must annotate other: &'a str and -> &'a str explicitly:

struct Parser;

impl Parser {
    // Explicitly tie the return lifetime `'a` to `input`, overriding Rule 3 (`&self`):
    fn strip_comment<'a>(&self, input: &'a str) -> &'a str {
        match input.find('#') {
            Some(i) => &input[..i],
            None => input,
        }
    }
}

fn main() {
    let p = Parser;
    println!("Code: '{}'", p.strip_comment("let x = 5; # comment"));
}
Code: 'let x = 5; '