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; '