Can you write this from memory?
Given `let s = String::from("hello");`, create an immutable reference `r` to `s`.
Lifetimes are Rust's way of ensuring references are always valid. The compiler tracks how long each reference lives and rejects code where a reference might outlive its data.
Most of the time, Rust figures out lifetimes automatically through "elision rules." But sometimes you need explicit annotations, and that's where the syntax gets tricky. Is it &'a str or 'a &str? What does 'static actually mean?
This page focuses on lifetime syntax until annotations become natural. From elision rules to explicit annotations to the common errors that send you searching.
The most common lifetime mistake. A function creates a value and tries to return a reference to it:
// WRONG: returning a reference to a local variable
// fn make_greeting() -> &str {
// let s = String::from("hello");
// &s // error[E0515]: cannot return reference to local variable
// }
// RIGHT: return an owned String instead
fn make_greeting() -> String {
String::from("hello")
}
The local String is dropped at the end of the function. A reference to it would point at freed memory. If the function creates the data, it must return the owned value. References only work for data that outlives the function call. For a refresher on when values are dropped, see the ownership and borrowing guide.
The compiler cannot infer which input reference the return borrows from:
// WRONG: two inputs, one output — which does the return borrow from?
// fn longest(x: &str, y: &str) -> &str {
// if x.len() > y.len() { x } else { y }
// // error[E0106]: missing lifetime specifier
// }
// RIGHT: tell the compiler both inputs and the output share a lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
The annotation 'a creates a relationship: at each call site, the returned reference is only valid for the shorter of the two input lifetimes, because both must be valid:
let result;
let string1 = String::from("long string");
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
println!("{result}"); // OK: both string1 and string2 are alive
}
// println!("{result}"); // ERROR: string2 is dropped, so 'a has ended
A reference outlives the data it points to:
// WRONG: r outlives x
// let r;
// {
// let x = 5;
// r = &x; // error[E0597]: x does not live long enough
// }
// println!("{r}");
// RIGHT: ensure the data lives at least as long as the reference
let x = 5;
let r = &x;
println!("{r}");
The fix is usually to move the owned value to a wider scope, or restructure so the reference doesn't escape.
A temporary (created mid-expression) is dropped at the end of the statement, but a reference to it escapes:
// WRONG: the String is a temporary, dropped at the semicolon
// let s: &str = &String::from("hello"); // error[E0716]
// RIGHT: bind the temporary to a variable first
let owned = String::from("hello");
let s: &str = &owned; // owned lives long enough
This often appears in method chains that return owned values. Break the chain: bind the intermediate to a let.
Rust applies three rules to infer lifetimes automatically. You only need explicit annotations when these rules don't resolve everything:
- Each reference parameter gets its own lifetime.
- If there is exactly one input lifetime, it is assigned to all output lifetimes.
- If there is a
&selfor&mut self, its lifetime is assigned to all output lifetimes.
// Rule 2: one input → one output (no annotation needed)
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
// Rule 3: &self method (no annotation needed)
impl<'a> Excerpt<'a> {
fn part(&self) -> &str {
self.part
}
}
// Elision FAILS: two inputs, one output — which does it borrow from?
// fn longest(x: &str, y: &str) -> &str // error[E0106]
// Must annotate: fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
Elision is only allowed when inference is unambiguous. When the compiler asks for annotations, it means the rules weren't enough. For a compact reference of these rules and common annotation patterns, see the lifetimes cheat sheet.
If a struct holds a reference, it needs a lifetime parameter. This tells the compiler: "this struct cannot outlive the data it borrows."
struct Excerpt<'a> {
part: &'a str,
}
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = &novel[..16];
let excerpt = Excerpt { part: first_sentence };
// excerpt cannot outlive novel
Every reference field adds a constraint. If the struct borrows from two independent sources, use two lifetime parameters:
struct Pair<'a, 'b> {
first: &'a str,
second: &'b str,
}
'static appears in two contexts with different meanings:
| Syntax | Meaning | Example |
|---|---|---|
&'static T | A reference valid for the entire program | let s: &'static str = "hello"; |
T: 'static | T contains no non-'static borrows (T is "self-contained") | fn spawn<T: Send + 'static>(f: T) |
// &'static str — string literals live forever
let s: &'static str = "hello";
// T: 'static — the type owns all its data (no borrowed fields)
// String satisfies 'static because it owns its data
// &'a str does NOT satisfy 'static (unless 'a is 'static)
fn send_to_thread<T: Send + 'static>(val: T) {
std::thread::spawn(move || {
println!("{val:?}");
});
}
T: 'static does not mean "lives forever" — it means "doesn't borrow anything short-lived." An owned String is 'static, but a &str with a limited lifetime is not.
Lifetime bounds express relationships between lifetimes and between lifetimes and types:
// 'a: 'b means 'a outlives 'b (data behind 'a lives at least as long as 'b)
fn first<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str
where
'a: 'b,
{
x
}
// T: 'a means T doesn't contain references shorter than 'a
fn store_ref<'a, T: 'a>(val: &'a T) {
// val is valid for 'a, and T contains nothing shorter
}
The most common lifetime bound is T: 'static, seen in thread::spawn, trait objects (Box<dyn Trait + 'static>), and async runtimes. The traits and generics guide covers how trait bounds and lifetime bounds interact in practice.
- The Rust Book: Lifetimes — annotations, elision, structs with references
- Rust By Example: Lifetimes — practical lifetime examples
- Rust Reference: Lifetime Elision — formal elision rules
- Common Rust Lifetime Misconceptions — deep dive on 'static and other confusions
When to Use Rust References & Lifetimes: Lifetime Annotations & Elision
- Add lifetime annotations when the compiler can't infer relationships.
- Use `'static` for data that lives for the entire program (string literals, etc.).
- Use explicit lifetimes when returning references from functions with multiple reference parameters.
- Annotate struct fields that contain references.
- Trust elision rules for simple cases. Don't over-annotate.
Check Your Understanding: Rust References & Lifetimes: Lifetime Annotations & Elision
What are lifetime annotations and why are they needed?
Lifetime annotations describe how long references are valid relative to each other. They don't change how long data lives. They help the compiler verify that references won't outlive their data. They're needed when the compiler can't infer the relationships automatically.
What You'll Practice: Rust References & Lifetimes: Lifetime Annotations & Elision
Common Rust References & Lifetimes: Lifetime Annotations & Elision Pitfalls
- Syntax error on `'a &str` -- the lifetime goes between `&` and the type. Write `&'a str`, not `'a &str`.
- Adding `'a` everywhere to silence the compiler -- lifetimes describe relationships, not ownership. Over-annotating hides the real issue. Let elision rules work and only annotate when the compiler asks.
- `error[E0515]` when returning a reference from a function -- the local data is dropped when the function returns. Return an owned type (`String`, `Vec<T>`) instead of a reference.
- Thinking `'static` means immutable or "lives forever" -- `&'static str` means the reference is valid forever, but `T: 'static` just means T doesn't contain short-lived borrows. An owned `String` satisfies `'static`.
- `error[E0716]` temporary value dropped while borrowed -- a temporary created in an expression is dropped at the semicolon, but a reference to it escapes. Bind the temporary to a `let` variable first.
- Assuming lifetime annotations change how long data lives -- they don't. Annotations only describe existing relationships so the compiler can verify them.
Rust References & Lifetimes: Lifetime Annotations & Elision FAQ
What are the lifetime elision rules?
1) Each reference parameter gets its own lifetime. 2) If there's exactly one input lifetime, it's assigned to all output lifetimes. 3) If there's a `&self` or `&mut self`, its lifetime is assigned to all output lifetimes.
What does 'static mean?
`'static` means the reference is valid for the entire program duration. String literals (`"hello"`) have type `&'static str`. It doesn't mean the data is immutable; it means it lives forever.
Why do structs with references need lifetime annotations?
The struct can't outlive the data it references. The lifetime annotation tells the compiler: "this struct is only valid as long as the referenced data."
Can I have multiple lifetime parameters?
Yes! `fn foo<'a, 'b>(x: &'a str, y: &'b str)` has two independent lifetimes. You need multiple when the references have different validity periods.
What does the error 'lifetime may not live long enough' mean?
A reference might outlive its data. Usually means you're returning a reference to something that will be dropped, or your lifetime annotations don't match the actual data flow.
What is the difference between &'static T and T: 'static?
`&'static T` is a reference valid for the entire program (like string literals). `T: 'static` means T contains no non-'static borrows — it's self-contained. An owned `String` satisfies `T: 'static` even though it's not a reference.
How do I fix E0716 (temporary value dropped while borrowed)?
Bind the temporary to a named variable. Instead of `let s: &str = &String::from("hello");`, write `let owned = String::from("hello"); let s: &str = &owned;`. The variable extends the temporary's lifetime.
Rust References & Lifetimes: Lifetime Annotations & Elision Syntax Quick Reference
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}let s: &'static str = "hello world";struct Excerpt<'a> {
part: &'a str,
}impl<'a> Excerpt<'a> {
fn level(&self) -> i32 { 3 }
}fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where 'a: 'b { x }fn spawn<T: Send + 'static>(val: T) { }fn first_word(s: &str) -> &str {
&s[..1]
}Rust References & Lifetimes: Lifetime Annotations & Elision Sample Exercises
Fill in the variable to dereference the reference.
rFill in the lifetime annotation for the return type.
&'aFill in the parameter type so Rust can elide the lifetime.
&str+ 35 more exercises
Copy-ready syntax examples for quick lookup