Lifetimes
Why lifetimes?
References borrow data. The compiler must ensure that no reference outlives the data it points to. Lifetimes are how you (or the compiler) name the span of time a reference is valid.
In C#: The GC makes object lifetime mostly invisible; you don’t annotate lifetimes. In Rust there is no GC for most data, so the compiler enforces that borrows don’t outlive the owner.
How long a borrow lasts (non-lexical lifetimes)
A borrow is alive only from where it is created up to its last use — not to the end of
the enclosing { } scope. This is "non-lexical lifetimes" (NLL):
let mut s = String::from("hi");
let r = &s; ┐
println!("{r}"); ┘ ← r's borrow ends here (its last use)
s.push_str("!"); ✓ OK: no borrow is alive anymoreIf you moved println!("{r}") to after s.push_str("!"), the shared borrow r would
still be alive when you mutate s, and the compiler would reject it (you can't mutate
through the owner while a shared borrow is live — see module 06). Lifetimes are just the
compiler reasoning about these spans; most of the time you never write one.
In C#: there is no borrow checker, so this aliasing-versus-mutation question is left to you at runtime; in Rust the answer is a compile-time guarantee.
When the compiler asks for lifetimes
Usually the compiler can infer lifetimes (elision). You need to add them when:
- A function returns a reference that depends on one of its arguments.
- There are multiple input references and the compiler can’t tell which one the return value follows.
Lifetime annotation syntax
Lifetime names start with ' (e.g. 'a). You attach them to references:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}This means: the returned reference lives as long as the shorter of x and y (both are 'a). The caller can use the result only as long as both inputs are valid.
In C#: No direct equivalent; C# doesn’t express “this reference is valid as long as that one.”
Elision rules
The compiler often infers lifetimes using a few rules (e.g. one input reference → output gets that lifetime; multiple inputs but one is &self → output gets self’s lifetime). If inference fails, you get an error and must add explicit lifetimes.
Key takeaway
Lifetimes describe how long references are valid. Add 'a (or similar) when the compiler asks, especially when returning a reference that depends on an argument. C# has no direct analogue; Rust’s borrow checker uses lifetimes to prevent use-after-free.