The page's longest example makes lifetimes sound like a function-signature problem — but a struct that stores a reference needs one too, and it's the first place most people meet the error. &str inside a struct means "borrowed from somewhere else", and the compiler won't guess how long that somewhere lives, so it refuses to build until the struct says so.
struct Excerpt {
part: &str,
}
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let e = Excerpt { part: first };
// error[E0106]: missing lifetime specifier
// part: &str, <- expected named lifetime parameter
// help: consider introducing a named lifetime parameterA 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 droppedThat's the same rule as longest, applied to a type instead of a function: a struct field holding a reference must say how long the borrow lasts. Adding a lifetime parameter to the struct (Excerpt<'a>) and tagging the field with it (&'a str) tells the compiler the struct can't outlive the string it borrows from — and every use of Excerpt is then checked against that:
struct Excerpt<'a> {
part: &'a str,
}
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let e = Excerpt { part: first };
println!("{}", e.part); // Call me IshmaelLifetime 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.