TL;DR: A lifetime is the scope for which a reference is valid. Rust tracks lifetimes at compile time to ensure references never outlive the data they point to. In most code, the compiler infers lifetimes automatically (lifetime elision). You only write explicit lifetime annotations (
'a) when the compiler cannot figure out the relationship between multiple references on its own.
What Is a Lifetime in Rust?
A lifetime is a label that tells the Rust compiler how long a reference is valid; ensuring that references never point to memory that has been freed.
Every reference in Rust has a lifetime, but you rarely see them written explicitly. The compiler infers lifetimes in most situations through a process called lifetime elision. You encounter explicit lifetime annotations only when functions or structs hold multiple references whose relationships the compiler cannot determine automatically.
Lifetimes do not change how long data lives; they are annotations that describe the scope of validity. They exist only at compile time and have no runtime cost.
What Does a Lifetime Annotation Look Like?
Lifetime annotations are written with a tick mark followed by a lowercase name: 'a, 'b, 'static.
// Without lifetime annotation (compiler infers it):
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
// With explicit lifetime annotation (required when compiler can't infer):
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}The annotation 'a in longest means: "the returned reference will be valid for at least as long as both x and y are valid." Without it, the compiler cannot know whether the return value points to x or y, and therefore how long it is safe to use.
When Do You Need Explicit Lifetimes?
You need explicit lifetime annotations in three situations: functions returning references derived from multiple input references, structs that hold references, and trait implementations involving references.
Function with multiple reference inputs:
// ❌ Won't compile; compiler doesn't know if return comes from x or y
fn longest(x: &str, y: &str) -> &str { ... }
// ✅ Explicit lifetime links return to inputs
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Struct holding a reference:
// Without lifetime, compiler doesn't know how long `text` must be valid
struct ImportantExcerpt<'a> {
text: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt { text: sentence };
// excerpt cannot outlive `novel`; compiler enforces this
}What Is Lifetime Elision?
Lifetime elision is the set of rules the compiler uses to infer lifetimes automatically, so you don't need to annotate them in common cases.
The three elision rules:
- Each reference parameter gets its own lifetime:
fn f(x: &str, y: &str)→fn f<'a, 'b>(x: &'a str, y: &'b str) - If there is exactly one input lifetime, it is assigned to all output lifetimes
- If one of the inputs is
&selfor&mut self, its lifetime is assigned to all outputs
// These are equivalent after lifetime elision:
fn first(s: &str) -> &str { ... }
fn first<'a>(s: &'a str) -> &'a str { ... }What Is 'static?
'static is a special lifetime meaning the reference is valid for the entire duration of the program; typically used for string literals and data embedded in the binary.
let s: &'static str = "I live for the whole program";
// String literals are always 'static:
fn greeting() -> &'static str {
"Hello, world!"
}'static bounds on trait objects (dyn Trait + 'static) mean the type holds no non-static references; commonly required when sending data across threads.
Frequently Asked Questions
Many developers say yes. The key insight is that lifetime annotations don't control how long data lives; they describe relationships between references so the compiler can verify safety. Once that mental model clicks, most lifetime errors become readable.
In most application code, yes. Lifetime elision handles the common cases. Lifetime annotations become necessary when writing generic library code, data structures that hold references, or functions that return references derived from multiple inputs.
A scope is a code region bounded by {}. A lifetime is the span of time a reference is valid, which often corresponds to a scope but can be more precise (thanks to NLL; Non-Lexical Lifetimes). Scopes are about code structure; lifetimes are about reference validity.
This means a reference you're trying to return or store might not be valid for as long as the compiler requires. The fix is usually to either return an owned value instead of a reference, or to add a lifetime annotation that explicitly connects the input and output lifetimes.
Sources
- The Rust Book; Validating References with Lifetimes: Official reference
- Rust Reference; Lifetime Elision
Related Glossary Terms
- Ownership: The system lifetimes are part of
- Borrow Checker: Enforces lifetime validity at compile time
- Trait: Often combined with lifetime bounds
Keep Reading
- Rust Lifetimes Deep Dive: the definitive guide to lifetimes in Rust
- Rust Ownership and Borrowing Explained: lifetimes are the formal expression of borrow rules
- Is Rust Hard to Learn?: lifetimes are consistently cited as the hardest part
