Lifetimes Explained
A lifetime is the span during which a reference is valid. Lifetime parameters like 'a relate references in function signatures and structs so the compiler can reject dangling pointers.
Recipe
fn first<'a>(items: &'a [i32]) -> Option<&'a i32> {
items.first()
}
fn main() {
let data = vec![3, 1, 4];
println!("{:?}", first(&data));
}When to reach for this: Returning references from functions, storing references in structs, or understanding "does not live long enough" errors.
Working Example
struct Excerpt<'a> {
text: &'a str,
}
fn excerpt<'a>(full: &'a str, end: usize) -> Excerpt<'a> {
Excerpt { text: &full[..end.min(full.len())] }
}
fn main() {
let article = String::from("Rust lifetimes are compile-time checks.");
let e = excerpt(&article, 4);
println!("{}", e.text);
}What this demonstrates:
- Struct holding a reference needs a lifetime parameter
Excerpt<'a>cannot outlive theStringit borrows- Returned excerpt borrows from
fullinput - Lifetime connects struct field to source data
Deep Dive
How It Works
- Every reference has an inferred or explicit lifetime
- Lifetime
'ameans "valid for some region at least as long as'a" - Subtyping: longer-lived data can satisfy shorter lifetime requirements
- Lifetimes are not stored at runtime
Why They Exist
Without lifetimes, this would compile unsafely:
// impossible in safe Rust
// fn dangle() -> &str { let s = String::from("x"); &s }Relationship to Ownership
- Owner dropped -> borrows end
- Lifetimes encode ordering constraints between owners and borrowers
'staticis the longest lifetime (entire program)
Gotchas
- Thinking lifetimes change runtime behavior - They are static analysis only. Fix: Focus on borrow regions, not runtime timers.
- Over-annotating every reference - Elision covers common cases. Fix: Annotate when compiler asks or signatures are public API.
- Returning reference from
ifwith different locals - Unified lifetime may fail. Fix: Return owned value or ensure both arms borrow same source. - Self-referential structs without care - Classic pitfall. Fix: Use indices,
Pin, or owned graph (Rc). - Confusing
'staticwith immortality - Means valid for program duration, not that data never frees if leaked differently. Fix: Read'staticpage.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Owned String | Independent result needed | Zero-copy view required |
Cow<'a, str> | Maybe borrow, maybe own | Always one strategy |
| Indices into parent buffer | Stable handles | Simple one-off substring |
Arc<str> | Shared immutable text | Single owner suffices |
FAQs
Are lifetimes like GC generations?
No. They are compile-time labels for borrow validity, not runtime memory management.
Can lifetimes be inferred?
Yes, in many local and function cases via elision rules.
What syntax is `'a`?
A lifetime parameter name. Convention starts at 'a, 'b. Could be 'line for clarity.
Do all structs need lifetimes?
Only those storing references. Owned fields need no lifetime params.
Can lifetimes be too short?
Yes - that is the borrow checker's job to catch when a ref would dangle.
What is higher-ranked lifetime `for<'a>`?
Quantifies over any lifetime - used with trait objects and some fn traits.
Lifetime in async futures?
Self-referential futures use Pin internally; do not hand-roll without understanding.
Relationship to scopes?
Scopes often align with lifetimes but NLL ends loans before scope end sometimes.
Can I compare lifetimes at runtime?
No. They do not exist at runtime.
Where next?
Lifetime Annotations for writing bounds explicitly.
Related
- Lifetime Annotations - writing
'a - Lifetime Elision - omission rules
'static& Bounds -'staticmeaning- Borrowing & References - loan rules
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.