Lifetimes & References

A lifetime is Rust's way of describing how long a reference stays valid. Most of the time the compiler infers them (elision); when you write them, they turn dangling-reference bugs into compile-time errors.

What a Lifetime Is

A lifetime is a scope during which a reference is valid. When a function returns a reference, Rust must know which input that reference could point into; the answer is the overlap of the input lifetimes:

Lifetimes: two input references with a returned reference bounded by their overlap

Figure 1 — a returned &'a cannot outlive the shortest input lifetime.

Lifetime Elision

Most lifetimes are elided: the compiler infers them from a few simple rules, so ordinary functions need no annotations at all. There is no lifetime syntax here, yet the code is fully checked:

fn first_word(s: &str) -> &str {   // lifetimes inferred
    s.split_whitespace().next().unwrap_or("")
}

fn main() {
    let text = String::from("hello world");
    let w = first_word(&text);
    println!("{w}");
}

Explicit Annotations

When a function takes two references and returns one, the compiler can no longer guess which input the output borrows from — so you annotate. The signature below says "the returned reference lives as long as the shorter of the inputs":

// both inputs and the output share lifetime 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() >= y.len() { x } else { y }
}

fn main() {
    let a = String::from("longer");
    let b = String::from("short");
    let result = longest(&a, &b);
    println!("longest: {result}");
}

Dangling References Are Prevented

The payoff is that returning a reference to data that is about to be dropped simply does not compile. These are the two classic failures the compiler catches:

fn main() {
    let _result;
    {
        let temp = String::from("soon gone");
        // _result = &temp;      // ERROR: `temp` does not live long enough
    }
    // println!("{_result}");    // would be a dangling reference
}

// fn broken() -> &str {
//     let local = String::from("gone");
//     &local                    // ERROR: cannot return reference to local
// }
Practice move: uncomment the commented lines above one at a time and read each diagnostic — the compiler names the exact lifetime that is too short.