Skip to content

Lifetimes

Rust’s borrow checker prevents dangling references — references that point to memory that has already been freed. In simple cases the compiler can figure out how long a reference is valid on its own. But when a function takes two references and returns one of them, the compiler needs help: which input does the output borrow from, and for how long?

Lifetime annotations answer that question. They are written as 'a (a tick followed by a short name) inside angle brackets.

Here is a function that takes two string slices and returns the longer one. The return value borrows from one of the inputs — but which one? It depends on the runtime values. The annotation 'a says: “the return value lives at most as long as the shorter of the two inputs.”

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

All three references share the same lifetime 'a. The compiler uses this to ensure the returned reference never outlives the data it points to.

Without the annotation, the compiler would reject this function: it cannot determine the return lifetime from the inputs alone.

Lifetime annotations exist precisely to catch this class of bug at compile time:

// This does NOT compile — the compiler prevents the dangling reference.
// fn dangle() -> &String {
// let s = String::from("hello"); // s is created here
// &s // we return a reference to s
// } // s is dropped here — the reference would dangle

Rust rejects the code above before it ever runs.

If a struct holds a reference, you must annotate the lifetime so the compiler knows the struct cannot outlive the data it borrows:

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

This says: “an Excerpt cannot outlive the string it borrows from.” The compiler enforces this guarantee automatically.

In many common patterns Rust can infer lifetimes automatically using three elision rules, so you do not need to write annotations every time:

  1. Each reference parameter gets its own lifetime. fn foo(x: &str, y: &str) becomes fn foo<'a, 'b>(x: &'a str, y: &'b str).
  2. If there is exactly one input reference, its lifetime is assigned to all output references. fn foo(x: &str) -> &str becomes fn foo<'a>(x: &'a str) -> &'a str.
  3. If one of the parameters is &self or &mut self, its lifetime is assigned to all output references.

When all three rules apply, no annotation is needed. When they do not fully determine the output lifetime (as in longest), you must annotate explicitly.

// A function with a lifetime annotation:
// the returned reference lives at least as long as the shorter of x or y.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// A struct that holds a reference — must annotate the lifetime.
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn announce(&self) -> &str {
self.text
}
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("longest: {}", result);
}
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find '.'");
let excerpt = Excerpt { text: first_sentence };
println!("excerpt: {}", excerpt.announce());
}
Why do lifetime annotations exist in Rust?
In the function signature `fn longest<'a>(x: &'a str, y: &'a str) -> &'a str`, what does the `'a` annotation guarantee?
When can the compiler infer lifetimes automatically without explicit annotations?
What does it mean to say that lifetime annotations are 'compile-time only'?