Overview
A lifetime is a name for a region of code during which a reference is valid — nothing
runs at runtime, nothing is allocated, and annotations never change how long anything
lives. They exist purely so function and type signatures can state relationships the
borrow checker then verifies: "the return value borrows from the first argument", "this
struct may not outlive the string it points into". Most lifetimes are elided; the craft
is knowing what the elision rules wrote for you, reading 'a as the relationship it
declares, and recognizing when a lifetime tangle is the design asking to own its data.
Key points
- Annotations describe, never prescribe:
'adoesn't make anything live longer — it's a variable the checker solves for. If there's no valid solution, the caller's code is what gets rejected; the signature is a contract, and lifetime parameters are part of a function's API exactly like types. - The three elision rules write most signatures for you: each reference parameter
gets its own lifetime; if there's exactly one input lifetime it's assigned to all
outputs; if
&self/&mut selfis present, its lifetime goes to the outputs. Explicit annotation is needed precisely where these leave ambiguity — multiple reference inputs feeding a reference output. - Structs holding references infect their users:
struct Parser<'a> { input: &'a str }declares "this value may not outlive the input it borrows" — correct for short-lived views (parsers, iterators), viral for long-lived state. A'aon a struct stored in another struct stored in an app context is the classic sign the field should own its data (String, orCow<'a, str>to defer the choice). 'staticmeans two different things: as a borrow (&'static str— lives for the whole program: literals, leaked boxes) and as a bound (T: 'static— the type contains no non-static borrows, i.e. "owns its data"). The second is whatthread::spawnandtokio::spawndemand, and misreading it as the first causes the "does my value have to live forever?!" panic — it doesn't; it has to be ownable.- Variance is why it mostly just works: a longer-lived reference coerces to a
shorter-lived one (
&'static strwhere&'a stris wanted) because&'a Tis covariant in'a;&'a mut Tis invariant inT, which is why some&mutsignatures are stricter than intuition expects. File under "know it exists" — the Rustonomicon has the full table. - HRTBs (
for<'a> Fn(&'a str) -> &'a str) quantify over all lifetimes — the checker writes them for you on most closure bounds; you write them by hand mainly for function-pointer/closure APIs that borrow their arguments. - GATs (stable 1.65) let a trait's associated type carry a lifetime —
type Item<'a>— unlocking lending iterators (yield borrows into self) and collection-agnostic APIs that were previously unwritable. - Restructure beats annotate: chasing a third explicit lifetime through a signature is the smell; the fixes are owning the data, splitting the type, indices over references, or narrowing the borrow's scope. Annotations are for stating simple relationships precisely — complex relationships belong in the design.
Examples
// Elision leaves this ambiguous — annotation states the relationship:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// T: 'static — "owns its data", not "lives forever":
fn spawn_worker<T: Send + 'static>(job: T) { /* thread::spawn(move || …) */ }
let owned = String::from("fine"); // moved in: satisfies 'static
// let borrowed = &local; // a &'a String would not
// Cow — defer the borrow-vs-own decision to runtime:
fn normalize(input: &str) -> std::borrow::Cow<'_, str> {
if input.contains('\r') { Cow::Owned(input.replace('\r', "")) }
else { Cow::Borrowed(input) }
}
Related
- Ownership, memory & concurrency — the model — parent map of the group.
- Ownership & borrowing — the rules these names annotate; NLL is what computes the actual regions.
- Memory without GC —
Cowand the own-vs-borrow spectrum in the smart-pointer landscape. - Fearless concurrency — where
T: 'staticbites: spawning. - The Rust Programming Language — ch. 10.3 for the core; the Book's later chapters use lifetimes in anger.
Citations
[1] The Book, ch. 10.3 — Validating References with Lifetimes [2] The Rustonomicon — Subtyping and Variance [3] The Rust Reference — Lifetime elision