Lifetimes
A lifetime is a compile-time tag on a reference that tells the borrow checker how long it’s valid. Most are inferred (elision); you write them when the compiler can’t tell what relates to what.
Function signatures, structs, common patterns
EXAMPLE
// 1) Lifetime elision — compiler infers in 90% of cases
fn first_word(s: &str) -> &str { // ← &str → &'a str inferred
s.split_whitespace().next().unwrap_or("")
}
// 2) Explicit lifetime — when multiple references are involved
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
// 3) Multiple lifetimes — when references don't tie together
fn first_or_default<'a, 'b>(s: &'a str, fallback: &'b str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
// returns something tied to 'a, not 'b — caller knows that
}
// 4) Structs holding references — must declare a lifetime
struct ParsedConfig<'a> {
name: &'a str,
section: &'a str,
}
impl<'a> ParsedConfig<'a> {
fn parse(input: &'a str) -> Self {
let mut parts = input.splitn(2, '.');
ParsedConfig {
section: parts.next().unwrap_or(""),
name: parts.next().unwrap_or(""),
}
}
}
// 5) Lifetime bounds on generics
struct Wrapper<'a, T: 'a> {
inner: &'a T,
}
// T: 'a means "T cannot contain references shorter than 'a".
// 6) The 'static lifetime
const NAME: &'static str = "Ada";
fn boxed_static<T: 'static>(t: T) -> Box<T> { Box::new(t) }
// 'static = lives for the entire program. Required for thread::spawn closures, etc.
// 7) Anonymous lifetimes — '_ when you don't need a name
fn build_iter(items: &[String]) -> impl Iterator<Item = &'_ str> {
items.iter().map(|s| s.as_str())
}
// 8) Lifetime in trait objects
fn make_obj<'a>(s: &'a str) -> Box<dyn Greet + 'a> {
// Box<dyn T> defaults to 'static lifetime — be explicit to allow shorter
Box::new(NameGreet { name: s })
}
// 9) Common errors + fixes
// 9a) "borrowed value does not live long enough"
// fn dangle() -> &String { // ERROR
// let s = String::from("hi");
// &s // s drops at end of fn
// }
// Fix: return the owned String, or a 'static reference.
fn fine() -> String {
String::from("hi") // owned
}
// 9b) Lifetime mismatch
// Caller passes references with different lifetimes — the compiler picks
// the SHORTER of the two. Tighten or rethink your signature.
// 10) Two lifetimes — return ties to ONE input
fn left<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str { x }
// 11) Higher-rank trait bounds (HRTB) — rare but powerful
fn apply_with_str<F: for<'a> Fn(&'a str) -> usize>(f: F) -> usize {
f("hello")
}
// 12) Bonus — Non-Lexical Lifetimes (NLL, since 2018 edition)
// Borrows end at LAST USE, not end of scope. Old style:
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{}", first); // borrow ends here under NLL
v.push(4); // legal
// 13) When to add explicit lifetimes
// • Function returns a reference but multiple refs in args (compiler doesn't know which)
// • Struct holds a reference
// • Generic types with reference parameters
// • Trait objects whose lifetime needs to be shorter than 'static
Why it matters
Most lifetime errors mean “you’re trying to hold a reference past the data it points to.” The fix is usually owning the data (clone, copy, return owned) rather than fighting the annotations.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
Try it Yourself »
Discussion
Loading…