title: Lifetimes
Lifetimes are how Rust tracks how long references are valid. Most of the time the compiler infers them, but sometimes you need to annotate them explicitly to help the borrow checker understand the relationship between references.
The problem lifetimes solve
Without lifetimes, this would be ambiguous — does the returned reference point to x or y?
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}The compiler rejects this because it can’t guarantee the returned reference lives long enough.
Why scope alone isn’t enough
Within a single function, the compiler can see everything and reason about it directly — no annotations needed:
let s = String::from("hello");
let r = &s; // compiler knows r can't outlive sThe problem arises when references cross function boundaries. When a function takes references in and returns a reference out, the compiler can’t tell which input the returned reference came from — the function body is opaque to the caller. Scope doesn’t help here because the relationship between inputs and output only exists inside the function, not at the call site.
Lifetime annotations are how you communicate that relationship across the boundary so the caller can verify it isn’t holding onto the result longer than the data it came from.
Lifetime annotations
Lifetime annotations use a ' prefix (usually 'a, 'b, etc.). They don’t change how long anything lives — they describe the relationship between lifetimes:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}This says: “the returned reference will be valid for at least as long as both x and y are valid.” The actual lifetime will be the shorter of the two.
Using the annotated function
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xy");
result = longest(s1.as_str(), s2.as_str());
println!("{result}"); // fine — both s1 and s2 are valid here
}
// println!("{result}"); // ERROR — s2 is dropped, result might point to itLifetime elision
Most functions don’t need explicit lifetime annotations because the compiler applies a set of elision rules automatically. These cover the common cases:
fn first_word(s: &str) -> &str { ... }
// equivalent to:
fn first_word<'a>(s: &'a str) -> &'a str { ... }You only need to annotate when the compiler can’t figure it out on its own — usually when there are multiple input references and the return type is also a reference.
Lifetimes in structs
If a struct holds a reference, it needs a lifetime annotation:
struct Important<'a> {
content: &'a str,
}
let novel = String::from("Call me Ishmael...");
let sentence = novel.split('.').next().unwrap();
let i = Important { content: sentence };This says the Important struct cannot outlive the reference it holds in content.
The static lifetime
'static means the reference lives for the entire duration of the program. String literals are 'static because they’re baked into the binary:
let s: &'static str = "I live forever";