Lifetimes and elision

A lifetime in Rust is the part of the type that says how long a borrow is valid. Most of the time it is invisible: the compiler infers it from the references in the signature, and the code looks like ordinary parameters. When the inference has two candidates and one return value, the programmer has to choose, and the syntax for that choice is one apostrophe and a name.

The lesson starts with the error, because that is where the concept becomes concrete. Then it adds the annotation, shows the elision rule that makes most functions work without one, and ends with the two places where a lifetime appears in a type: a struct that borrows, and a literal that lives for the whole program.

Two references, one return

The function below takes two string slices and returns the longer one as a slice. The signature says that the result is a borrow, but not which input it borrows from, so the compiler cannot tell how long the result may be used. It reports E0106 and shows both candidates.

The note is the clearest sentence in the message: the return type contains a borrowed value and the signature does not say whether it is borrowed from a or from b. The help writes the signature that would answer the question.

fn longest(a: &str, b: &str) -> &str { if a.len() >= b.len() { a } else { b } } fn main() { let first = String::from("longer"); let second = String::from("short"); println!("{}", longest(&first, &second)); }

rustc --edition 2024 needs_lifetime_err.rs

error[E0106]: missing lifetime specifier
--> needs_lifetime_err.rs:1:33
|
1 | fn longest(a: &str, b: &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 `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
| ++++ ++ ++ ++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0106`.

This is not a limitation to work around. The caller has to know how long the result lives, and the answer changes the program: the borrow is valid while both inputs are, which is the shorter of the two in practice.

A named lifetime

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str says that both inputs and the output share one lifetime. The name is a parameter of the function, declared in angle brackets like a type parameter, and the body does not change at all.

The compiler checks that the result really can live for 'a: the returned slice is one of the inputs, so the claim holds. The program prints the longer of the two words.

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b } } fn main() { let first = String::from("abc"); let second = String::from("defg"); println!("{}", longest(&first, &second)); }

rustc --edition 2024 named.rs
./named

defg

A lifetime name does not extend anything: it describes a relationship the compiler already checks. Two references with different lifetimes would need two names and a rule for which one the output follows; the single name is enough for every function in this part of the course.

When elision fills it in

One input reference and one output reference need no name: the output gets the lifetime of the input. That is the first elision rule, and it is why a function that returns the first letter of a string compiles as written.

The program below returns a slice of its only argument. The compiler fills in the same 'a the previous section wrote by hand, and the call site looks identical.

fn first_letter(text: &str) -> &str { &text[..1] } fn main() { let word = String::from("rust"); println!("{}", first_letter(&word)); }

rustc --edition 2024 one_input.rs
./one_input

r

The other rules cover methods: a method with &self gives its output the lifetime of the receiver, and a function with no reference input has no lifetime to give. The lesson on methods relies on the first of those.

A struct that holds a borrow

A struct with a reference field needs a lifetime parameter, because the struct must not outlive the value it borrows. The declaration puts the name after the struct name, and the field type uses it; the value is then built from a slice of a longer-lived string.

The program below borrows the first five bytes of a string into a struct and prints the field. The struct is alive only while the string is, which the compiler checks at the point where the struct is created.

struct Excerpt<'a> { part: &'a str, } fn main() { let text = String::from("hello world"); let excerpt = Excerpt { part: &text[..5] }; println!("{}", excerpt.part); }

rustc --edition 2024 struct_ref.rs
./struct_ref

hello

A struct that owns its field instead, with a String, needs no lifetime and outlives the string it was built from. The lesson on structs builds both and compares the two declarations.

The static lifetime

'static is the lifetime of a value that lives for the whole program. String literals have it, because they are stored in the binary; a function that returns one can say so, and the caller may keep the result forever.

The program below returns a literal and prints it. Nothing is allocated and nothing is borrowed from a local, so the annotation is the honest description of the value.

fn title() -> &'static str { "Rust" } fn main() { let name = title(); println!("{name}"); }

rustc --edition 2024 static_life.rs
./static_life

Rust

A 'static reference is not a licence to leak memory: it means the value is valid for the program, and only values that really are may carry it. The lesson on const and static items uses the same lifetime for a global string.

Exercises

  1. Add a third argument to the failing function and give all three the same lifetime. Does the body need to change?
  2. Make the struct own a String instead of borrowing a slice. Which parts of the declaration change?
  3. Write a function with one reference input that returns a reference to the last character. Why does it need no lifetime name?

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does a lifetime in a type describe?

    Answer

    How long a borrow is valid

    A lifetime is the part of the type that says how long the borrow may be used.

  2. Why does a function returning a borrow from two inputs need a named lifetime?

    Answer

    Because the compiler cannot tell which input the result borrows from

    The signature must say how long the result lives, and with two candidates only the programmer can choose.

  3. When does elision give the output the lifetime of the input?

    Answer

    When there is exactly one input reference

    With one input reference there is only one candidate, so the compiler fills it in.

  4. What does the static lifetime mean?

    Answer

    The value lives for the whole program

    A static borrow is valid for the whole run, which is true of literals stored in the binary.

  5. What is the first line of the compiler output for this program? fn pick(a: &str, b: &str) -> &str { if a.len() > b.len() { a } else { b } } fn main() { println!("{}", pick("abc", "de")); }

    Answer

    error[E0106]: missing lifetime specifier

    The return type is a borrow and the signature does not say which input it comes from.

  6. What does this program print? fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b } } fn main() { let first = String::from("abc"); let second = String::from("defg"); println!("{}", longest(&first, &second)); }

    Answer

    defg

    The second word has four bytes and the first has three, so the longer one is returned.

  7. What does this program print? fn first_letter(text: &str) -> &str { &text[..1] } fn main() { let word = String::from("rust"); println!("{}", first_letter(&word)); }

    Answer

    r

    Elision gives the output the lifetime of the only input, and the first byte is r.

  8. What does this program print? struct Excerpt<'a> { part: &'a str, } fn main() { let text = String::from("hello world"); let excerpt = Excerpt { part: &text[..5] }; println!("{}", excerpt.part); }

    Answer

    hello

    The struct borrows the first five bytes and prints them while the string is alive.

  9. What does this program print? fn title() -> &'static str { "Rust" } fn main() { let name = title(); println!("{name}"); }

    Answer

    Rust

    The literal lives in the binary for the whole program, so the static lifetime is accurate.

  10. What does this program print? fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() > b.len() { a } else { b } } fn main() { let a = "short"; let b = "longer"; println!("{}", longest(a, b)); }

    Answer

    longer

    Both literals are static strings, and the function returns the one with more bytes.

The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev