What Are Lifetimes?
Lifetimes are Rust's way of ensuring that references are always valid. Every reference in Rust has a lifetime — the scope for which that reference is valid. Most of the time, lifetimes are inferred automatically. When they can't be, you add lifetime annotations to tell the compiler how references relate to each other.
Key Insight
Lifetime annotations don't change how long references live. They describe the relationships between lifetimes of multiple references so the borrow checker can verify safety. Think of them as contracts, not commands.
Why the Borrow Checker Needs Lifetimes
// This function returns a reference, but to WHICH input?
// The compiler can't tell without a lifetime annotation.
// Won't compile without lifetimes:
// fn longest(x: &str, y: &str) -> &str {
// if x.len() > y.len() { x } else { y }
// }
// With lifetime annotations:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives at least as long
// as the SHORTER of x and y's lifetimes
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("Longest: {result}"); // OK: both alive here
}
// println!("{result}"); // ERROR if result could be string2
// The compiler enforces that result doesn't outlive string2
}
Lifetime Annotation Syntax
// Lifetime parameters start with ' and are usually short
// Convention: 'a, 'b, 'c, etc.
&i32 // a reference (lifetime is inferred)
&'a i32 // a reference with an explicit lifetime 'a
&'a mut i32 // a mutable reference with lifetime 'a
// Multiple lifetime parameters
fn multi<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
// Return type tied to 'a, so only x can be returned
x
}
// The return type's lifetime must match one of the input lifetimes
// (or be 'static). You can't return a reference to local data.
Lifetime Elision Rules
The compiler applies three elision rules to infer lifetimes automatically. If all references can be resolved by these rules, you don't need annotations.
- Each reference parameter gets its own lifetime.
fn foo(x: &str, y: &str)becomesfn foo<'a, 'b>(x: &'a str, y: &'b str) - If there's exactly one input lifetime, it's assigned to all output references.
fn foo(x: &str) -> &strbecomesfn foo<'a>(x: &'a str) -> &'a str - If one parameter is
&selfor&mut self, its lifetime is assigned to all outputs.
Methods that return references usually borrow from self.
// These are equivalent — the compiler elides the lifetime:
fn first_word(s: &str) -> &str { /* ... */ }
fn first_word<'a>(s: &'a str) -> &'a str { /* ... */ }
// Elision works for methods:
struct Parser {
input: String,
}
impl Parser {
// &self lifetime is automatically applied to output
fn peek(&self) -> &str {
&self.input[..1]
}
// Equivalent to: fn peek<'a>(&'a self) -> &'a str
}
Lifetimes in Structs
// Structs that hold references need lifetime annotations
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3 // No reference in return — no annotation needed
}
fn announce(&self, announcement: &str) -> &str {
// Elision rule 3: return lifetime = &self lifetime
println!("Attention: {announcement}");
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel
.split('.')
.next()
.expect("Could not find a '.'");
let excerpt = Excerpt {
part: first_sentence,
};
println!("Excerpt: {}", excerpt.part);
// excerpt cannot outlive novel (the data it references)
}
The 'static Lifetime
// 'static means the reference lives for the entire program
// String literals are always 'static
let s: &'static str = "I live forever";
// Common in error types and trait bounds
fn make_error() -> Box {
Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"something went wrong"
))
}
// Be careful: 'static doesn't always mean what you think
// It means "CAN live that long", not "MUST live that long"
fn generic_print(val: T) {
println!("{val}");
}
// String is 'static (it owns its data, no references)
// &String is NOT 'static (unless the String is leaked)
When to Use 'static
Don't reach for 'static as a quick fix for lifetime errors. Usually it means
you should restructure your code to use owned types (String instead of
&str) or properly annotate the actual lifetimes.
Key Takeaways
- ✅ Lifetimes describe how long references are valid relative to each other
- ✅ The compiler's elision rules handle most cases automatically
- ✅ Structs holding references must declare lifetime parameters
- ✅
'staticmeans the reference can live for the entire program duration - ✅ Lifetime annotations are contracts — they don't change actual lifetimes