Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every lifetime you'll meet

Elided lifetimes, named 'a parameters, references in structs, 'static, lifetime bounds, higher-ranked bounds and variance. What each annotation says, and what fixes a lifetime error.

A lifetime annotation never changes how long anything lives. It describes which borrow a reference came from, so the compiler can check the caller. Work down this list.

  • Most functions → write nothing. The elision rules fill in the lifetimes (one reference input, or a &self method).
  • E0106: the output borrows from one of several inputs → name a lifetime, 'a, on the inputs the result can come from and on the output.
  • A struct holds a reference → the struct gets a lifetime parameter, and can't outlive what it borrows. Often an owned field (String instead of &str) is simpler.
  • You see 'static → either a value that lives for the whole program (a string literal), or a bound meaning "owns all its data" (what thread::spawn and tokio::spawn ask for).
  • A generic stores a T that may contain references → a bound such as T: 'a.
  • A closure must accept references of any lifetime → a higher-ranked bound, for<'a> Fn(&'a str). Usually elided too.
  • A borrow error you can't annotate away → the fix is almost always in ownership: return an owned value, clone, or restructure so the borrow ends sooner.

The idea

The stretch of code where a borrow is used.

Formal. A lifetime is a region of the program during which a reference must be valid. Names like 'a in a signature state relationships between the regions of inputs and outputs, and the compiler checks every call against them. They never change when any value is dropped: a value still lives exactly until the end of its owner's scope (or until it is moved or consumed), and a reference that would outlive it is rejected (E0597, E0515) no matter how it is annotated.

In plain English. A lifetime label is a promise written on a reference, "this stays good at least as long as that," and the compiler checks the promise. Writing the label can't make anything last longer, the way writing a later date on a carton doesn't keep the milk fresh.

Example. fn shorter<'a>(x: &'a str, y: &'a str) -> &'a str says the result is valid only while both arguments are. Calling it with a string that is dropped at the end of an inner block and then using the result after that block is error E0597.

Why this rule exists. Annotations let the compiler check each function on its own, from its signature, without looking into the bodies of the functions it calls.

An annotation is a claim the compiler checks, never an instruction to keep something alive.

Formal. Every reference must remain within the lifetime of its referent; code in which a reference could be used after its referent is dropped does not compile (E0597, "borrowed value does not live long enough"). A function can't return a reference to one of its own local variables.

In plain English. You can't hold on to a pointer to something that has already been cleaned up. Rust checks this before the program runs.

Example. let r; { let x = 5; r = &x; } println!("{r}"); is E0597: x is dropped at the inner closing brace while r still refers to it.

Why this rule exists. Using memory after it has been freed is one of the most dangerous bugs in systems code. Rust makes it a compile error.

The rule all of this exists to enforce.

Formal. The compiler considers a reference alive from where it is created to the last place it is used, not to the end of the enclosing block. A conflicting borrow that starts after that last use is allowed.

In plain English. Once you've finished using a reference, Rust treats it as returned, even if its name is still in scope.

Example. let r = &v; println!("{r:?}"); v.push(4); compiles: r's last use is the println!, so the mutable borrow for push doesn't overlap it. Moving the println! after push makes it E0502.

Why this rule exists. Tying borrows to actual use rather than to braces lets many correct programs compile without contortions, while still rejecting every real overlap.

A borrow ends at its last use, not at the end of the block.

In function signatures

Section

Elision rules

The three rules that fill in most signatures for you.

Formal. In a function signature, a reference type written without a lifetime gets one by three rules, which look only at the signature: (1) each elided lifetime in the parameters becomes its own distinct lifetime parameter; (2) if there is exactly one input lifetime, it is assigned to every elided output lifetime; (3) if there are several input lifetimes but one of them is &self or &mut self, self's lifetime is assigned to every elided output lifetime. If an output lifetime is still unassigned, the signature is error E0106 and must be annotated.

In plain English. Most of the time Rust can guess how a returned reference is tied to the inputs: one reference in, so the result must come from it; a method, so the result probably comes from the object. When there's more than one possibility, you have to say which.

Example. fn first_word(s: &str) -> &str means fn first_word<'a>(s: &'a str) -> &'a str (rule 2). fn pick(x: &str, y: &str) -> &str is E0106: two inputs, no self.

Why this rule exists. Writing lifetimes out every time is noise for the common cases, which the rules cover, while ambiguous cases still have to be spelled out, so no guess is ever silently wrong.

Each input reference gets its own; one input's flows to the output; &self's wins in a method.

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str, and what the caller must then guarantee.

In types and bounds

A struct that borrows can't outlive its source.

Section

'static

A reference that's valid forever, and the different T: 'static bound.

Section

Lifetime bounds

T: 'a, and the + '_ on an impl Trait that borrows.

for<'a>: a closure that works for every lifetime a caller might pass.

Section

Variance

Why a &'static str can go where a &'a str is expected, and a &mut can't be shortened the same way.

When it goes wrong

E0597, E0515, E0716, and the ownership change that fixes each.

The earlier recipe for the same family of errors, before lifetimes had names.