06 / 20 · Day 2
Day 2 · Concept 06

Lifetimes 101

Every reference has a lifetime — a region of code over which it stays valid. Almost always inferred. The annotations 'a only appear when the compiler can't decide on its own. The day you can read function signatures with lifetime annotations is the day Rust feels native.


1 · The intuition

A lifetime is a region of code. The compiler tracks: "this reference must not be used outside this region". Most of the time it figures it out via three elision rules and you never write a 'a in your life. When you do see one, it's a contract: the returned reference lives at least as long as the inputs marked with the same lifetime.

2 · Lifetime elision — the three rules

  1. Each input reference gets its own lifetime.
  2. If there is exactly one input lifetime, it is assigned to the output.
  3. If one of the inputs is &self or &mut self, that lifetime is assigned to all output references.

Those three rules cover almost every function you'll write. If they don't, the compiler tells you exactly which annotation is missing.

3 · Try it

rust src/main.rs · explicit lifetime
// 'a says: the returned &str lives at least as long as both inputs.
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("long string here");
    let result;
    {
        let s2 = String::from("short");
        result = longest(s1.as_str(), s2.as_str());
        println!("longest: {}", result);
    }
    // println!("{}", result); // ERROR if uncommented — result might reference s2 which is gone
}

4 · The 'static lifetime

Special: 'static means "lives for the entire program". String literals are &'static str — they're baked into the binary. Reach for 'static when you really do mean "never freed".

rust src/main.rs
let s: &'static str = "this is in the binary";
let n: &'static i32 = &42;  // primitives can be static refs too

5 · Common mistakes

  • Adding 'a annotations when not needed. Elision usually handles it. Let the compiler ask first.
  • Returning a reference to a local. Doesn't compile. Return owned data instead, or refactor so the caller passes the storage.
  • Struct fields that are references. Require lifetime annotation: struct Foo<'a> { s: &'a str }. Then every method/function using Foo carries the lifetime.
  • Mistaking 'static for "anywhere is fine". It means "lives forever" — a constraint. Use sparingly.

6 · When it clicks

  • You read fn foo<'a>(x: &'a str) -> &'a str as "the result borrows from x".
  • Most days you don't write 'a at all — elision handles it.
  • Struct-with-references is something you reach for deliberately, not by accident.
Found this useful?