Skip to content

Beginner · Borrowing, slices and lifetimes · topic 17 of 50 · 35 min

Lifetimes

Explanation

A lifetime is the stretch of code during which a reference is valid. Usually the compiler works lifetimes out by itself. When a function returns a reference that could come from more than one input, you name the lifetime, as in fn longest<'a>(x: &'a str, y: &'a str) -> &'a str. The annotation does not make anything live longer; it only describes how the references relate.

Why lifetimes exist

Rule 2 of borrowing says a reference must never outlive the value it points to. The compiler checks this by tracking a lifetime for every reference: the region of code where it is valid. Most of the time this is invisible. You only write lifetimes when the compiler cannot work out, from a function's signature alone, how long a returned reference is valid.

output
let r;                 // ---------+-- 'a (r)
{                      //          |
    let x = 5;         // -+-- 'b  |
    r = &x;            //  |       |
}                      // -+       |   x dropped here
println!("{r}");       //          |   r used here: x is gone!
                       // ---------+
The reference r lives longer ('a) than the value x ('b). The compiler rejects this with E0597.

A function that needs an annotation

output
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}
// error[E0106]: missing lifetime specifier
// help: this function's return type contains a borrowed value, but the
//       signature does not say whether it is borrowed from `x` or `y`
The result might point into x or into y. The compiler needs to know which, to check the caller.

The fix is a lifetime parameter. It is written with an apostrophe and a short name, usually 'a, declared in angle brackets after the function name, like a generic.

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
Read it as: for some region 'a, both inputs are valid for at least 'a, and the result is valid for 'a.

In practice, 'a becomes the shorter of the two input lifetimes. The caller may use the result only while BOTH inputs are still alive.

rust
fn main() {
    let a = String::from("Thiruvananthapuram");
    let result;
    {
        let b = String::from("Goa");
        result = longest(&a, &b);
        println!("{result}");      // OK: a and b are both alive
    }
    // println!("{result}");       // error[E0597]: `b` does not live long enough
}
Even though result happens to point to a, the compiler only knows "it came from a or b", so it must assume the shorter one.
Careful: Lifetime annotations never change how long a value lives. They are labels that describe relationships so the compiler can check them. You cannot make a value live longer by writing 'static.

Lifetime elision: why you rarely write them

Elision means "leaving out". The compiler applies three rules to fill in lifetimes for you:

  1. Each reference parameter gets its own lifetime.
  2. If there is exactly one input lifetime, it is given to all output references. That is why fn first_word(s: &str) -> &str needs no annotation.
  3. If there is a &self or &mut self parameter (a method), its lifetime is given to the outputs.

If the rules still leave an output lifetime unknown, as with longest (two inputs, no self), you must write it.

Structs that hold references

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

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first = novel.split('.').next().unwrap();
    let e = Excerpt { part: first };
    println!("{}", e.part);   // Call me Ishmael
}
An Excerpt cannot outlive the text it borrows from; the 'a on the struct says so.
Tip: Beginners should prefer owned fields (String instead of &str) in structs. It avoids lifetimes completely and is usually fast enough. Use reference fields when you have a measured reason.

'static

'static is a special lifetime meaning "valid for the whole program". String literals are &'static str because they are stored inside the program file. When the compiler suggests adding 'static, it is usually a sign to return an owned String instead, not to make your data static.

Common mistakes

  • Adding 'static to silence an error. It rarely fixes the real problem.
  • Thinking 'a makes a value live longer. It does not; it only describes.
  • Putting references in structs everywhere. Start with owned data.
  • Annotating every function. Let elision work; add lifetimes only when the compiler asks.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Lifetimes · 1/8

Lifetime = where a reference is valid

A lifetime is the stretch of code where a reference is valid. The compiler tracks one for every reference.

Prefer a video by a teacher? Find free videos on “rust lifetimes explained for beginners elision” on YouTube ↗

In real life: Parsing a log line without copying

A monitoring tool at a hosting company reads millions of log lines a minute. Copying every field into a new String would waste memory. Instead, a LogEntry struct holds &str slices that point into the original line, with a lifetime tying the entry to the line. The compiler guarantees no entry is used after its line is freed, and nothing is copied.

rust
struct LogEntry<'a> {
    level: &'a str,
    message: &'a str,
}

fn parse(line: &str) -> Option<LogEntry<'_>> {
    let (level, message) = line.split_once(": ")?;
    Some(LogEntry { level, message })
}

fn main() {
    let line = String::from("ERROR: payment gateway timeout");
    if let Some(e) = parse(&line) {
        println!("[{}] {}", e.level, e.message);
    }
}
Prints: [ERROR] payment gateway timeout. '_ asks the compiler to use the elided lifetime; ? returns None early (module 6).

Practice (0/3 done)

Task 1 · Question

Which of these function signatures needs an explicit lifetime annotation?

Task 2 · Question

What does 'a in fn longest<'a>(x: &'a str, y: &'a str) -> &'a str do?

Task 3 · Practice

Write fn shorter<'a>(x: &'a str, y: &'a str) -> &'a str. Call it so that it compiles, then move one String into an inner block so the result is used after the block, read the E0597 error, and fix it. Finally, write a struct Quote<'a> { text: &'a str, by: &'a str } and print one quote.

Quick quiz

Question 1

1.What is a lifetime?

Question 2

2.Do lifetime annotations change how long values live?

Question 3

3.Why does fn first(s: &str) -> &str need no annotation?

Question 4

4.What lifetime do string literals have?

Question 5

5.The compiler suggests adding 'static to a returned reference to a local String. What is usually the right fix?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is a lifetime?

Answer: The region of code where a reference is valid

Lifetimes describe where references are valid.

Do lifetime annotations change how long values live?

Answer: No, they only describe relationships between references

Annotations are checked labels, not instructions to keep data alive.

Why does fn first(s: &str) -> &str need no annotation?

Answer: Elision: with one input lifetime, the output gets it

Elision rule 2 gives the single input lifetime to the output.

What lifetime do string literals have?

Answer: 'static

Literals live inside the program binary for the whole run.

The compiler suggests adding 'static to a returned reference to a local String. What is usually the right fix?

Answer: Return the owned String instead

A local String is dropped at the end of the function; returning ownership is the clean fix.

Which statement about non-lexical lifetimes is correct?Related

Answer: Borrows last until their last use

NLL ends borrows at their last use.

From Interview questions: ownership, borrowing and lifetimes

How long does a static local variable live?Related

Answer: The whole program run

Static storage duration.

From Scope and storage classes

Which lifetime is usual for an EF Core DbContext in ASP.NET Core?Related

Answer: Scoped

AddDbContext registers it as scoped: one per request.

From Dependency injection and service lifetimes

What is wrong with: const std::string& name() { std::string s = "Asha"; return s; }Related

Answer: It returns a reference to a local that is destroyed: a dangling reference

s dies when the function returns. Return std::string by value.

From References: another name for a variable

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Borrowing, slices and lifetimes”. The module exam comes next.
  2. Module 4 exam: Borrowing, slices and lifetimes · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Structs, enums and pattern matching. 50 topics left in the course before the final exam.

← Previous: Slices and strings