Skip to content

Rust Lifetimes

Lifetimes are usually the point where new Rust developers hit their first real wall. This page keeps things gentle — the goal is just to recognize what a lifetime is and why the compiler asks for one, not to master every rule around them.

You already know from borrowing that a reference (&) lets you use a value without owning it. But a reference is only valid as long as the value it points to still exists. Rust needs to make sure you never end up with a reference to something that’s already been cleaned up — that class of bug is called a dangling reference, and it’s a classic source of crashes in languages like C.

Most of the time, Rust figures this out on its own by looking at your code’s scopes — you never have to think about it (this is exactly what you saw on the Scope page). But sometimes, especially with functions, the compiler can’t work it out by itself and asks you to spell it out. That’s what a lifetime annotation is.

Here’s a function that returns the longer of two string slices:

fn longest(a: &str, b: &str) -> &str {
if a.len() > b.len() { a } else { b }
}

This won’t compile. The error looks like:

error[E0106]: missing lifetime specifier

The problem: the returned reference could be borrowed from a or from b, and Rust can’t tell which one just by reading the function — so it can’t guarantee the reference you get back is still valid by the time you use it.

You fix it by naming a lifetime (by convention, a lowercase letter like 'a) and telling Rust that the return value’s lifetime is tied to both parameters:

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("hi");
let result = longest(&s1, &s2);
println!("Longest: {result}");
}

Output:

Longest: hello
▶ Try it Yourself

Read 'a as a label, not a value — it doesn’t set an actual duration. It just tells Rust “these references all need to stay valid for at least the same stretch of code.” The compiler then checks every call site to make sure that’s actually true.

Without this annotation, the compiler genuinely can’t tell whether the reference longest hands back might outlive one of the strings it came from. The lifetime annotation is what lets it prove — at compile time, with zero runtime cost — that the reference you get back will always be valid to use.

You’ll write 'a far less often than this page might suggest. Plenty of Rust code never needs an explicit lifetime, because the compiler can infer them in most everyday situations. You mainly run into them when a function returns a reference and the compiler can’t work out on its own where it came from — at that point, just remember: it’s not a bug, it’s the compiler asking you to confirm something it can’t prove by itself.

  • A lifetime tells the compiler how references relate to each other so it can rule out dangling references.
  • You only need to write one when the compiler can’t infer it on its own — most commonly, functions that return a reference.
  • 'a doesn’t set a duration — it links the lifetimes of different references so the compiler can check them.
  • This is entirely a compile-time check; it adds no runtime cost.

Quick check

1. What problem do lifetimes solve?

2. Why did fn longest(a: &str, b: &str) -> &str fail to compile without a lifetime annotation?

3. Does adding a lifetime annotation cost anything at runtime?

4. According to this page, when do you most commonly need to write an explicit lifetime?

Score: 0 / 4