Rust path · Ownership · Topic 4 of 12

Lifetimes

Not started

A **lifetime** is the stretch of the program during which a reference is valid. Inside a function the compiler works lifetimes out on its own. At function boundaries it can't see the bodies of callers and callees, so signatures must say how the references going in and coming out are related. That is all a lifetime parameter such as 'a does: it names a region so the signature can say "the result borrows from this input". It never changes how long any value actually lives.

Why it matters: Lifetime annotations look intimidating but say something simple: which input a returned reference borrows from. Reading them lets you design APIs that borrow instead of clone.

  1. 1Every reference has a lifetime

    It is the region of code in which the reference is valid. The compiler infers it inside function bodies.

  2. 2Annotations relate, they don't extend

    'a in a signature says the output borrows from these inputs. It never makes any value live longer.

  3. 3Elision covers the common cases

    With one reference input, or a &self method, the output lifetime is filled in for you. You write them only when it is ambiguous.

  4. 4Structs that borrow carry one

    A struct holding a reference, such as Parser<'src>, can't outlive the data it points into.

1

How it works

The explanation, with examples you can run.

When you have to write them

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {    if x.len() >= y.len() { x } else { y }} struct Parser<'src> {    input: &'src str, // a Parser cannot outlive the text it points into} impl<'src> Parser<'src> {    fn first_word(&self) -> &'src str {        self.input.split_whitespace().next().unwrap_or("")    }} fn first_char(s: &str) -> &str { // elided: same as fn first_char<'a>(s: &'a str) -> &'a str    &s[..1]} fn main() {    let a = String::from("ownership");    let w;    {        let b = String::from("borrow");        println!("{}", longest(&a, &b)); // fine: used while both are alive        let p = Parser { input: &a };        w = p.first_word(); // tied to a, not to p    }    println!("{w} {}", first_char("rust"));}

It prints ownership, then ownership r. Without the annotations, longest fails with:

error[E0106]: missing lifetime specifier  |1 | fn longest(x: &str, y: &str) -> &str {  |               ----     ----     ^ expected named lifetime parameter  |  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`

With 'a on both inputs, the result is valid for the region where both are valid. The caller can't keep the result after either string is dropped.

first_word returns &'src str, not a borrow of self. That is why w stays valid after p is gone: it points into a, which is still alive. Choosing which lifetime an output carries is real API design.

Elision rules

Most signatures need no annotations because of three elision rules:

  1. Each elided lifetime in the inputs gets its own parameter.
  2. If there is exactly one input lifetime, it is used for every elided output lifetime.
  3. If one input is &self or &mut self, its lifetime is used for elided outputs.

If the rules don't settle an output's lifetime, you must write it. Since Rust 1.89 a lint, mismatched_lifetime_syntaxes, warns when a signature hides a lifetime in one place but shows it in another, such as fn make(s: &str) -> Thing where Thing secretly borrows from s; writing -> Thing<'_> makes the borrow visible.

'static

'static means the data lives until the program ends. String literals are &'static str. As a bound, T: 'static means the type holds no borrowed data that could expire. Owned types like String satisfy it. That is why std::thread::spawn and tokio::spawn require it: the new thread or task may outlive the caller's stack.

When lifetimes fight you

If a struct needs several lifetime parameters, or you keep reaching for 'static to make errors go away, the data probably wants to be owned: store a String, or share it with Rc or Arc. Borrowing structs work best for short-lived views such as parsers, iterators and guards.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.fn longest(x: &str, y: &str) -> &str doesn't compile. Why?
  2. 2.What does 'a mean in fn first<'a>(s: &'a str) -> &'a str?
  3. 3.What does a &'static str guarantee?

0 of 3 right so far. Get all three to finish this topic.