Rust Fundamentals: Ownership, Errors and Fearless Concurrency · Errors, Collections, Traits and Concurrency

Lifetimes Without the Fear

Lifetimes are how the compiler checks that references never outlive the data they point to. Most are inferred; you write them only when a function returns a reference that comes from one of several inputs, or a struct holds a reference.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

Functions and a struct that return or store references safely: longer(a, b) that returns one of two string slices, and a Highlight struct that points into a document instead of copying it.

The idea

Every reference has a lifetime: the span of code where it's valid. The borrow checker makes sure no reference outlives its data. Usually the compiler infers lifetimes (elision rules), so you never write them. You need annotations when the compiler can't tell which input a returned reference comes from:

fn longer<'a>(a: &'a str, b: &'a str) -> &'a str

Read 'a as: "the result lives at least as long as the shorter of a and b." Annotations don't change how long anything lives; they describe relationships so the compiler can check callers.

Structs holding references need a lifetime parameter too (struct Highlight<'a> { text: &'a str }), meaning a Highlight can't outlive the document it points into.

'static means "lives for the whole program", which is true of string literals.

If lifetimes get complicated, owning the data (String instead of &str) is often the simpler design.

The code

Returning and storing references with lifetimes
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}

// Elision: one input reference, so the output borrows from it; no annotation needed.
fn first_sentence(text: &str) -> &str {
    text.split('.').next().unwrap_or(text).trim()
}

struct Highlight<'a> {
    text: &'a str, // points into the document; can't outlive it
    note: String,
}

impl<'a> Highlight<'a> {
    fn show(&self) -> String {
        format!("\"{}\" ({})", self.text, self.note)
    }
}

fn main() {
    let doc = String::from("Ownership keeps Rust safe. Borrowing keeps it fast. Lifetimes connect the two.");

    println!("first sentence: {}", first_sentence(&doc));

    let a = String::from("borrow checker");
    let result;
    {
        let b = String::from("lifetimes");
        result = longer(&a, &b);
        println!("longer: {result}"); // fine: used while both a and b are alive
    }
    // println!("{result}"); // error: `b` does not live long enough

    let hl = Highlight { text: &doc[27..51], note: String::from("key idea") };
    println!("{}", hl.show());

    let motto: &'static str = "Fearless concurrency"; // string literals live for the whole program
    println!("{motto}");
}

Output

first sentence: Ownership keeps Rust safe
longer: borrow checker
"Borrowing keeps it fast." (key idea)
Fearless concurrency

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Adding lifetimes everywhere. Most functions need none; add them when the compiler asks.
  • Thinking 'a extends a lifetime. It only describes constraints; it can't make data live longer.
  • Fighting the borrow checker with complex lifetimes in long-lived structs; owned data or indexes are often simpler.
  • Returning references to locals. No annotation can fix that; return an owned value.

Key points

  • Lifetimes ensure references never outlive their data.
  • Usually inferred; annotate when a returned reference could come from several inputs.
  • Structs holding references need lifetime parameters.
  • When in doubt, own the data (String, Vec).

Further reading: Validating references with lifetimes.

Practice questions

When do you need to write lifetime annotations on a function?

Answer

When it returns a reference and the elision rules can't determine which input it borrows from, typically with two or more reference parameters (e.g. fn longer<'a>(a: &'a str, b: &'a str) -> &'a str).

Do lifetime annotations change how long values live?

Answer

No. They describe relationships between the lifetimes of references so the compiler can check that callers never use a reference after its data is gone.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?