A lifetime is the span of code for which a reference remains valid. Most of the time, lifetimes are inferred automatically and never need to be written. Explicit lifetime annotations (written 'a) become necessary when the compiler cannot work out on its own how the lifetimes of several references relate — most commonly, a function that returns a reference derived from one of its parameters. The annotation is purely a description for the compiler to check; it has zero effect on how long anything actually lives at runtime.
The problem lifetimes solve
Without an annotation, this function would not compile: given two references, the compiler cannot tell on its own whether the returned reference's validity depends on the first parameter, the second, or both.
// fn longest(x: &str, y: &str) -> &str { // COMPILE ERROR: missing lifetime specifier
// if x.len() > y.len() { x } else { y }
// }Annotating the relationship
The annotation <'a> says: the returned reference is valid for exactly as long as BOTH x and y are valid — it's a constraint the compiler then checks at every call site, rejecting any call where the returned reference could outlive the data it points to.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
let s1 = String::from("long string");
let result;
{
let s2 = String::from("short");
result = longest(s1.as_str(), s2.as_str());
println!("{}", result); // fine — s2 still alive here
}
// println!("{}", result); // would be a COMPILE ERROR here — s2 already droppedLifetime elision: most of the time, you write nothing
The compiler applies a small set of well-defined rules (lifetime elision) that correctly infer lifetimes for the overwhelming majority of functions — a single-reference-parameter function, or a method taking &self, essentially never needs an explicit annotation. Explicit lifetimes only surface for genuinely ambiguous cases like longest above, where multiple reference parameters could plausibly relate to the return value.