Files
Sprimo/skills/m01-ownership/patterns/lifetime-patterns.md
2026-02-12 22:58:33 +08:00

4.4 KiB

Lifetime Patterns

Basic Lifetime Annotation

When Required

// ERROR: missing lifetime specifier
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}

// FIX: explicit lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Lifetime Elision Rules

  1. Each input reference gets its own lifetime
  2. If one input lifetime, output uses same
  3. If &self or &mut self, output uses self's lifetime
// These are equivalent (elision applies):
fn first_word(s: &str) -> &str { ... }
fn first_word<'a>(s: &'a str) -> &'a str { ... }

// Method with self (elision applies):
impl MyStruct {
    fn get_ref(&self) -> &str { ... }
    // Equivalent to:
    fn get_ref<'a>(&'a self) -> &'a str { ... }
}

Struct Lifetimes

Struct Holding References

// Struct must declare lifetime for references
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn level(&self) -> i32 { 3 }

    // Return reference tied to self's lifetime
    fn get_part(&self) -> &str {
        self.part
    }
}

Multiple Lifetimes in Struct

struct Multi<'a, 'b> {
    x: &'a str,
    y: &'b str,
}

// Use when references may have different lifetimes
fn make_multi<'a, 'b>(x: &'a str, y: &'b str) -> Multi<'a, 'b> {
    Multi { x, y }
}

'static Lifetime

When to Use

// String literals are 'static
let s: &'static str = "hello";

// Owned data can be leaked to 'static
let leaked: &'static str = Box::leak(String::from("hello").into_boxed_str());

// Thread spawn requires 'static or move
std::thread::spawn(move || {
    // closure owns data, satisfies 'static
});

Avoid Overusing 'static

// BAD: requires 'static unnecessarily
fn process(s: &'static str) { ... }

// GOOD: use generic lifetime
fn process<'a>(s: &'a str) { ... }
// or
fn process(s: &str) { ... }  // lifetime elision

Higher-Ranked Trait Bounds (HRTB)

for<'a> Syntax

// Function that works with any lifetime
fn apply_to_ref<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let s = String::from("hello");
    let result = f(&s);
    println!("{}", result);
}

Common Use: Closure Bounds

// Closure that borrows any lifetime
fn filter_refs<F>(items: &[&str], pred: F) -> Vec<&str>
where
    F: for<'a> Fn(&'a str) -> bool,
{
    items.iter().copied().filter(|s| pred(s)).collect()
}

Lifetime Bounds

'a: 'b (Outlives)

// 'a must live at least as long as 'b
fn coerce<'a, 'b>(x: &'a str) -> &'b str
where
    'a: 'b,
{
    x
}

T: 'a (Type Outlives Lifetime)

// T must live at least as long as 'a
struct Wrapper<'a, T: 'a> {
    value: &'a T,
}

// Common pattern with trait objects
fn use_trait<'a, T: MyTrait + 'a>(t: &'a T) { ... }

Common Lifetime Mistakes

Mistake 1: Returning Reference to Local

// WRONG
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // s dropped, reference invalid
}

// RIGHT
fn no_dangle() -> String {
    String::from("hello")
}

Mistake 2: Conflicting Lifetimes

// WRONG: might return reference to y which has shorter lifetime
fn wrong<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    y  // ERROR: 'b might not live as long as 'a
}

// RIGHT: use same lifetime or add bound
fn right<'a>(x: &'a str, y: &'a str) -> &'a str {
    y  // OK: both have lifetime 'a
}

Mistake 3: Struct Outlives Reference

// WRONG: s might outlive the string it references
let r;
{
    let s = String::from("hello");
    r = Excerpt { part: &s };  // ERROR
}
println!("{}", r.part);  // s already dropped

// RIGHT: ensure source outlives struct
let s = String::from("hello");
let r = Excerpt { part: &s };
println!("{}", r.part);  // OK: s still in scope

Subtyping and Variance

Covariance

// &'a T is covariant in 'a
// Can use &'long where &'short expected
fn example<'short, 'long: 'short>(long_ref: &'long str) {
    let short_ref: &'short str = long_ref;  // OK: covariance
}

Invariance

// &'a mut T is invariant in 'a
fn example<'a, 'b>(x: &'a mut &'b str, y: &'b str) {
    *x = y;  // ERROR if 'a and 'b are different
}

Practical Impact

// This works due to covariance
fn accept_any<'a>(s: &'a str) { ... }

let s = String::from("hello");
let long_lived: &str = &s;
accept_any(long_lived);  // 'long coerces to 'short