iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…