Skip to content

Rust Lifetimes

Lifetimes are usually the point where new Rust developers hit their first real wall. This page keeps things gentle — the goal is just to recognize what a lifetime is and why the compiler asks for one, not to master every rule around them.

You already know from borrowing that a reference (&) lets you use a value without owning it. But a reference is only valid as long as the value it points to still exists. Rust needs to make sure you never end up with a reference to something that’s already been cleaned up — that class of bug is called a dangling reference, and it’s a classic source of crashes in languages like C.

Most of the time, Rust figures this out on its own by looking at your code’s scopes — you never have to think about it (this is exactly what you saw on the Scope page). But sometimes, especially with functions, the compiler can’t work it out by itself and asks you to spell it out. That’s what a lifetime annotation is.

Here’s a function that returns the longer of two string slices:

fn longest(a: &str, b: &str) -> &str {
if a.len() > b.len() { a } else { b }
}

This won’t compile. The error looks like:

error[E0106]: missing lifetime specifier

The problem: the returned reference could be borrowed from a or from b, and Rust can’t tell which one just by reading the function — so it can’t guarantee the reference you get back is still valid by the time you use it.

You fix it by naming a lifetime (by convention, a lowercase letter like 'a) and telling Rust that the return value’s lifetime is tied to both parameters:

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("hi");
let result = longest(&s1, &s2);
println!("Longest: {result}");
}

Output:

Longest: hello
▶ Try it Yourself

Read 'a as a label, not a value — it doesn’t set an actual duration. It just tells Rust “these references all need to stay valid for at least the same stretch of code.” The compiler then checks every call site to make sure that’s actually true.

Without this annotation, the compiler genuinely can’t tell whether the reference longest hands back might outlive one of the strings it came from. The lifetime annotation is what lets it prove — at compile time, with zero runtime cost — that the reference you get back will always be valid to use.

The next place you’ll meet lifetimes is in a struct. So far, every struct field has owned its data, like a String. A field can also be a reference, but then the struct is only valid as long as the data it points to. Rust makes you write that down with a lifetime:

struct Highlight<'a> {
text: &'a str, // borrows from some String that lives elsewhere
}
fn main() {
let book = String::from("Call me Ishmael. Some years ago...");
let first = Highlight { text: &book[..15] };
println!("Highlight: {}", first.text);
println!("The whole book is {} bytes long", book.len());
}

Output:

Highlight: Call me Ishmael
The whole book is 34 bytes long
▶ Try it Yourself

Read Highlight<'a> as “a Highlight that borrows something for the lifetime 'a”. The &book[..15] part is a slice: it borrows the first 15 bytes of book without copying them. Because first only borrows, book is still usable afterward.

The lifetime is what lets Rust catch this mistake, where the String is dropped while the struct still points into it:

struct Highlight<'a> {
text: &'a str,
}
fn main() {
let highlight;
{
let book = String::from("Call me Ishmael. Some years ago...");
highlight = Highlight { text: &book[..15] };
} // book is dropped here...
println!("{}", highlight.text); // ...but highlight still points into it
}
error[E0597]: `book` does not live long enough
--> src/main.rs:9:40
|
8 | let book = String::from("Call me Ishmael. Some years ago...");
| ---- binding `book` declared here
9 | highlight = Highlight { text: &book[..15] };
| ^^^^ borrowed value does not live long enough
10 | } // book is dropped here...
| - `book` dropped here while still borrowed
11 |
12 | println!("{}", highlight.text); // ...but highlight still points into it
| -------------- borrow later used here

A practical tip: if lifetimes in a struct are getting in your way, store an owned String instead of a &str. It copies the text, but the struct no longer depends on anything else, and the lifetime disappears. Many experienced Rust programmers start that way and only switch to references when they have a reason to.

You’ll write 'a far less often than this page might suggest. Plenty of Rust code never needs an explicit lifetime, because the compiler can infer them in most everyday situations. You mainly run into them when a function returns a reference and the compiler can’t work out on its own where it came from — at that point, just remember: it’s not a bug, it’s the compiler asking you to confirm something it can’t prove by itself.

Write a function shortest that takes two string slices and returns the shorter one. If they are the same length, return the first. Call it with "elephant" and "cat".

Hint: it has the same shape as longest above. You need <'a> after the name, and 'a on both parameters and the return type.

Show solution
fn shortest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() <= b.len() { a } else { b }
}
fn main() {
let big = String::from("elephant");
let small = String::from("cat");
println!("Shortest: {}", shortest(&big, &small));
println!("Tie goes to the first: {}", shortest("dog", "cow"));
}

Output:

Shortest: cat
Tie goes to the first: dog
▶ Try it Yourself

The returned slice might come from a or from b, so Rust needs 'a to know the result is only valid while both inputs are. Using <= instead of < makes a tie pick a.

Make a struct Quote with two &str fields, text and author. Create one where text borrows the first 20 bytes of the String "Fearless concurrency is a goal of Rust", and author is the literal "The Rust Book". Print it as Fearless concurrency (The Rust Book).

Hint: like Highlight above, the struct needs a lifetime: struct Quote<'a>, with &'a str for both fields. &speech[..20] is a slice of the first 20 bytes.

Show solution
struct Quote<'a> {
text: &'a str,
author: &'a str,
}
fn main() {
let speech = String::from("Fearless concurrency is a goal of Rust");
let quote = Quote {
text: &speech[..20],
author: "The Rust Book",
};
println!("{} ({})", quote.text, quote.author);
}

Output:

Fearless concurrency (The Rust Book)
▶ Try it Yourself

quote borrows from speech, so it can’t outlive it. The literal "The Rust Book" is baked into the program and lives for the whole run, so it easily lasts long enough for 'a. Try creating speech inside an inner { } block and printing the quote after the block ends, like the error example above, and watch the compiler stop you.

  • A lifetime tells the compiler how references relate to each other so it can rule out dangling references.
  • You only need to write one when the compiler can’t infer it on its own — most commonly, functions that return a reference.
  • 'a doesn’t set a duration — it links the lifetimes of different references so the compiler can check them.
  • A struct that holds a reference needs a lifetime too (struct Highlight<'a>), and can’t outlive the data it borrows. Storing an owned String avoids that.
  • This is entirely a compile-time check; it adds no runtime cost.

Quick check

1. What problem do lifetimes solve?

2. Why did fn longest(a: &str, b: &str) -> &str fail to compile without a lifetime annotation?

3. Does adding a lifetime annotation cost anything at runtime?

4. According to this page, when do you most commonly need to write an explicit lifetime?

Score: 0 / 4