← Back to Rust series
🦀
Ownership
Ownership · Prerequisite: lesson 08

09. Lifetimes — explicit 'a and elision rules

A lifetime is a compile-time marker for how long a reference is valid. You write them as `'a`, but most of the time the compiler infers them through elision rules. This lesson covers why lifetimes exist, when to write them by hand, and the meaning of `'static`.

Rustlifetime'aelisionstaticreference
Duration
~1.5 hours
Level
📊 Intermediate
Prerequisite
🎯 Lesson 08
OUTCOME
A lifetime is a compile-time marker for how long a reference is valid. You write them as `'a`, but most of the time the compiler infers them through elision rules. This lesson covers why lifetimes exist, when to write them by hand, and the meaning of `'static`.

What you'll learn

  • 1See why lifetimes prevent dangling references
  • 2Annotate functions and structs with `'a`
  • 3Recite the 3 lifetime elision rules
  • 4Distinguish `'static` from other lifetimes
  • 5Read and resolve lifetime-related compile errors

Overview

A reference whose target dies becomes a dangling reference. Rust prevents this at compile time by tracking lifetimes for every reference. Most code never spells them out — but functions that take references and return references sometimes need annotations.

Core Concepts

1) The problem lifetimes solve

rust
fn main() {
    let r;
    {
        let x = 5;
        r = &x;   // x is about to die...
    } // x dropped
    println!("{}", r); // error: `x` does not live long enough
}

2) Explicit annotation 'a

When a function takes references and returns one, the compiler needs to relate them. `'a` is any name (commonly a, b, c) marking a shared lifetime.

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

3) Elision — 3 rules

  1. Each reference parameter gets its own lifetime
  2. If there's one parameter, its lifetime is assigned to all output lifetimes
  3. If there's `&self` or `&mut self`, its lifetime is assigned to all output lifetimes

When inference succeeds via these rules, no annotation is needed — which is why everyday code rarely shows `'a`.

4) 'static — program-long

`&'static str` is valid for the program's entire run. String literals are the classic example.

Hands-on Examples

Two-input case that needs annotation:

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("short");
        result = longest(s1.as_str(), s2.as_str());
        println!("longest = {}", result);
    } // s2 dropped — result valid only inside the inner scope
}

A struct holding a reference must declare a lifetime:

rust
struct Excerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let e = Excerpt { part: first_sentence };
    println!("{}", e.part);
}

Elision in action — no annotation needed:

rust
// elidable — one input ref, rule 2 applies
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' { return &s[0..i]; }
    }
    s
}
// fully written: fn first_word<'a>(s: &'a str) -> &'a str { ... }

Common Mistakes

Q. How far should I spell lifetimes out?

A. Only when the compiler asks. Errors point out where elision can't decide — then add annotations there. No need to pre-emptively scatter `'a` everywhere.

Q. Is `'static` a magic catch-all?

A. No. `'static` means "lives forever" — that's a **strong constraint**. Short-lived references can't satisfy it. Sprinkling `'static` on signatures reduces caller flexibility a lot.

Q. What if two references have different lifetimes?

A. Declare both with `<'a, 'b>` and optionally an outlives bound `'b: 'a`. Usually you can simplify by tying both to the shorter of the two — when lifetime algebra gets ugly, your design probably needs a rethink.

Recap

  • Lifetimes mark how long references are valid for the compiler
  • Elision auto-fills most signatures
  • Annotations are needed when functions relate refs in to refs out
  • `'static` = whole program, not a free pass

Try It Yourself

  1. Try `longest` with two different lifetimes `'a, 'b` and read the error
  2. Define `struct Book<'a> { title: &'a str, author: &'a str }` and use it
  3. Write code that creates a dangling reference on purpose, then copy the compiler's exact message
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗