Skip to content

Lifetimes

The ‘a sticker

You’ve been seeing references since the ownership chapter — &T, &mut T, &str. Every one of those references has a hidden tag attached to it: a lifetime. The lifetime is just a name for “how long this reference is allowed to point at the thing it points at.”

Most of the time the compiler figures the lifetime out for you and you never see it. But occasionally — when a function returns a reference, or a struct holds one — you have to write the lifetime out by hand so the compiler can verify your math. That’s what 'a is. It’s a sticker the compiler can read.

Lifetimes scare people because of how they look. They are, secretly, the most boring concept in Rust. You’ll learn three rules and then never have to think about it again for the rest of your career.

Why we need them at all

Consider this function. It takes two string slices and returns whichever one is longer.

🦀 main.rs

Run that. The compiler complains:

missing lifetime specifier… this function’s return type contains a borrowed value, but the signature does not say whether it is borrowed from a or b

The compiler needs to know: when the caller hangs onto the returned &str, which input is it actually borrowing from? If the answer is “could be either,” then the returned reference must live no longer than the shorter of the two inputs.

We tell it that with a lifetime annotation:

🦀 main.rs

The <'a> introduces a lifetime parameter named a. We then stamp it on both inputs and on the output. The signature now reads, in English: “there exists some lifetime 'a; both inputs must live at least that long, and the output will live exactly that long.”

The compiler picks the actual concrete lifetime when you call the function — it picks the shortest one that satisfies the constraints.

Lifetimes elision — the rules you don’t have to write

If lifetimes were always required, Rust code would be a sea of 'as. They’re not, because of three lifetime elision rules the compiler applies to function signatures:

  1. Every input reference gets its own implicit lifetime parameter.
  2. If there’s exactly one input lifetime, it’s assigned to all output references.
  3. If one of the input references is &self or &mut self, its lifetime is assigned to all output references.

Those three rules cover the vast majority of real-world functions, so you almost never write 'a for input/output references. Watch:

🦀 main.rs

Not a single explicit lifetime, and yet every reference is correctly tracked. The elision rules are doing the work behind the scenes.

You only write a lifetime when the elision rules can’t figure it out — which in practice means: a function with multiple input references that returns a reference, where the compiler can’t tell which input the output is borrowed from.

Lifetimes on structs

If a struct holds a reference, the struct itself needs a lifetime parameter so the compiler can verify the borrowed thing outlives the struct.

🦀 main.rs

The <'a> after the struct name declares “this struct has some lifetime parameter I’m going to call 'a.” Every reference field with that lifetime must outlive the struct itself. The compiler enforces this automatically.

If you tried to make sentence go out of scope before h, the compiler would gently scream at you.

The 'static lifetime — “lives forever”

There’s one special lifetime, 'static, which means “lives for the entire program.” String literals get this lifetime automatically — they live in the binary’s read-only data section and are never freed.

🦀 main.rs

You’ll see 'static when:

  • A function returns a literal or other compile-time constant string.
  • You’re working with constants: const FOO: &'static str = "…";
  • You’re sending data between threads (we’ll talk about why in the concurrency chapter).

You should not use 'static to “make the borrow checker shut up.” That’s a trap; it usually means you’ve got an ownership problem you haven’t solved. Real 'static data is rare.

A worked example — the borrow that has to end

Here’s a classic situation that trips people up:

🦀 main.rs

The return type (&str, &str) has two references. Why doesn’t the compiler need lifetime annotations here? Elision rule 2: there’s only one input lifetime (&str), so both output references inherit it. Both a and b borrow from sentence, and the compiler verifies that sentence outlives the println.

Now try this variation — note carefully what’s different:

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

Two input references, neither one is &self. Elision can’t decide. You have to write the lifetime out (as we did at the top of the chapter). The compiler isn’t being mean — it literally has no way to know whether the output borrows from a or from b.

A foot-gun and how to spot it

The most common lifetime error in real code:

🦀 main.rs

That compiles and runs fine — elision handles it. But watch what happens if you try to make it work across function boundaries with the wrong shape:

fn longest_string() -> &String { // 💥 — borrowed from where?
let v = vec![String::from("oops")];
&v[0]
}

You can’t return a reference to a local. v would be dropped at function exit; the reference would dangle. The compiler refuses, with one of its classic error messages: “returns a reference to data owned by the current function.” The fix is to return an owned String, not a &String.

What you take away

  • Every reference has a lifetime; most of the time the compiler infers it.
  • You write 'a when there are multiple input references and the compiler can’t tell which one the output borrows from.
  • Structs with reference fields need a lifetime parameter so the compiler can ensure the struct doesn’t outlive its data.
  • 'static means “lives for the whole program” — rare, often a sign that you meant to own data, not borrow it.

The thing nobody tells you about lifetimes: once you grok them, they disappear. You’ll spend ten minutes a year writing 'a. The other 99.99% of the time, the compiler quietly does it for you.

Onward to smart pointers, where we start building data structures whose ownership isn’t a single tree.