Rust Lifetime Elision
Lifetime elision is Rust's set of fixed rules for filling in lifetimes. The rules run when a signature leaves lifetimes unwritten.
Resolve it yourself: one input
Cover the letters and apply the rules in order. Rule 1 gives the one input its own letter. Rule 2 hands that letter to the output. Resolved: fn last_word<'a>(s: &'a str) -> &'a str
fn last_word(s: &str) -> &strResolve it yourself: a method with a guest
Two inputs means two letters from Rule 1, so Rule 2 cannot fire — it needs exactly one input lifetime. Rule 3 can: the output takes the lifetime of &self. Resolved: fn banner<'a, 'b>(&'a self, mark: &'b str) -> &'a str. The guest's letter, 'b, appears on its input and nowhere else
fn banner(&self, mark: &str) -> &strResolve it yourself: nowhere to take from
Two inputs, no &self, one output. Rule 1 gives two letters. Rule 2 needs a single input lifetime. Rule 3 needs a &self. Nothing settles the output. So elision gives up. The programmer must write the relationship. This failure is what the three rules exist to explain:
fn choose(x: &str, y: &str) -> &str // error E0106: missing lifetime specifierThe '_ placeholder: elided on purpose
Writing '_ asks for the same guessing, but says out loud that a lifetime belongs in that spot. It is the preferred spelling inside types — you will meet it in return types like Thing<'_> and in std signatures:
fn first_word(s: &'_ str) -> &'_ str {
match s.find(' ') {
Some(i) => &s[0..i],
None => s,
}
}
let line = String::from("borrow checker");
println!("first = {}", first_word(&line));Example
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b' ' { return &s[0..i]; }
}
s
}
struct Excerpt<'a> { text: &'a str }
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 { 3 }
fn announce(&self, note: &str) -> &str { println!("note: {}", note); self.text }
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = first_word(&novel);
println!("first word = {}", first);
let ex = Excerpt { text: first };
println!("level = {}", ex.level());
println!("text = {}", ex.announce("new excerpt"));
}Key points
- Rule 1: each reference parameter gets its own lifetime — one input gets one letter, two inputs get two
- Rule 2: if there is exactly one input lifetime, it is assigned to every output reference
- Rule 3: in a method, if one input is &self, the output gets the lifetime of self
- A dangling reference is what all of this prevents.
- Error E0597 means one thing.
- A value did not live long enough for a reference to it.
- Elision also runs inside fn pointer types.
- fn(&str) -> &str quietly means the callable works for every lifetime.
- Written out, it is for<'a> fn(&'a str) -> &'a str.
- When the rules settle every reference, no annotation is needed.
- Elision fills in unwritten lifetimes by fixed rules.
In this example, first_word needs no annotation. Rule 1 gives its one input a lifetime. Rule 2 gives that same lifetime to the output. So it returns Call. Excerpt holds a reference, so its struct names the lifetime in its type. Its method announce takes &self and another reference. It returns a reference too. Rule 3 gives the output the lifetime of self. So the returned text is the excerpt itself. That is the word Call again. The Higher-Ranked Trait Bounds lesson in Rust Advanced shows the written-out form, for<'a> fn(&'a str) -> &'a str.
Note
Elision is not full guessing.
If any reference is still unsettled after all three rules, the compiler stops.
It stops with an error.
It asks you to state the relationship.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why does longest need a written lifetime but first_word does not?