Introduction
Every reference in Rust has a lifetime - the scope for which that reference is valid. Most of the time lifetimes are inferred automatically by the compiler, but sometimes, especially with functions that take and return references, the compiler needs explicit lifetime annotations to understand how the lifetimes of different references relate to each other. Lifetimes don't change how long anything actually lives at runtime; they are purely a compile-time tool the borrow checker uses to reject code that could produce a dangling reference.
Cricket analogy: Every fielding position (reference) is only valid for as long as that over lasts; usually the captain infers who covers where, but for tricky cross-field throws (functions returning references) the captain must explicitly call out the assignment, purely as a pre-match plan that prevents a fielder from chasing a ball that's already dead (dangling reference).
Syntax
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
struct Excerpt<'a> {
part: &'a str,
}
fn static_str() -> &'static str {
"lives for the whole program"
}Explanation
The generic lifetime parameter 'a in fn longest<'a>(x: &'a str, y: &'a str) -> &'a str tells the compiler that the returned reference will be valid for the smaller of the lifetimes of x and y - it doesn't change anything at runtime, it just describes a constraint the compiler must verify. Most functions don't need explicit annotations because of lifetime elision rules: if a function has exactly one reference parameter, its lifetime is assigned to the output; if it has &self, self's lifetime is assigned to the output. Explicit annotations are only required when the relationship between input and output lifetimes is ambiguous, such as when multiple reference parameters could be tied to the return value. The special 'static lifetime means the reference is valid for the entire duration of the program - string literals have this lifetime because they're embedded directly in the binary.
Cricket analogy: A function picking the "longest partnership" between two batting pairs returns validity for whichever pair's stand ends first, like fn longest<'a>; a single-batsman-input rule (elision) auto-assigns validity without asking, but multi-partner comparisons need explicit annotation, while a player's permanent franchise record ('static) lasts the entire tournament like a string literal baked into the program.
Example
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("Longest inside block: {}", result);
}
// Using `result` here would fail to compile if it referenced string2,
// because string2's lifetime ends at the closing brace above.
}Output
Longest inside block: long string is longKey Takeaways
- A lifetime describes how long a reference remains valid; it is checked at compile time only.
- Lifetime elision rules let the compiler infer lifetimes for most common function signatures automatically.
- Explicit annotations like <'a> are needed when the compiler can't infer how input and output reference lifetimes relate.
- fn longest<'a>(x: &'a str, y: &'a str) -> &'a str ties the return value's validity to the shorter of the two input lifetimes.
- 'static denotes a reference valid for the entire duration of the program, such as string literals.
Practice what you learned
1. What does a lifetime annotation like 'a actually do at runtime?
2. In fn longest<'a>(x: &'a str, y: &'a str) -> &'a str, what does the shared 'a tell the compiler?
3. Why don't most Rust functions need explicit lifetime annotations?
4. What does the 'static lifetime mean?
5. What problem do lifetimes primarily prevent?
Was this page helpful?
You May Also Like
Borrowing and References in Rust
How Rust lets code access data without taking ownership, using immutable and mutable references checked by the borrow checker.
Ownership in Rust
Rust's core memory-safety system where every value has exactly one owner and is dropped when that owner goes out of scope.
Structs in Rust
Structs let you group related values into a single named type with fields you access by name.
Generics in Rust
Generics let you write functions, structs, and enums that work over many types while remaining zero-cost at runtime.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics