A lifetime is a label that tells the compiler "the period during which a reference is valid." All references we've seen in Chapters 6–8 actually had lifetimes attached, but the compiler mostly filled them in automatically. In this chapter, we summarize when and why lifetimes need to be specified, and the rules by which the compiler allows them to be omitted.
Learning Objectives
- Understand why lifetimes are needed from the perspective of preventing dangling references.
- Learn the basic pattern of specifying
'ain function signatures. - Attach lifetime parameters to structures that hold references.
- Remember the three lifetime elision rules and know when omission is not allowed.
Key Concepts
1) Why lifetimes are needed
fn longest(a: &str, b: &str) -> &str { /* ... */ } // ❌ Does not compile
The compiler cannot know whether a returned reference belongs to a or b. If the lifetime of one of the inputs ends after the call, the returned reference could become dangling. Therefore, lifetimes are specified.
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { ... }
'a is a label that refers to "the common valid period of the references that this function handles."
2) Lifetimes in function signatures
- Declare lifetime parameters using
<'a>. - If the same label is attached to both input and output references, the "output lifetime is at least the intersection of the input lifetimes."
3) Lifetimes in structures
A struct that has a reference as a field must have a lifetime parameter.
struct Excerpt<'a> { part: &'a str }
A Excerpt instance cannot live longer than the value part points to.
4) Three Rules for Omitting Lifetimes
| # | Rule |
|---|---|
| 1 | Each input reference is assigned its own lifetime. |
| 2 | If there is exactly one input reference, that lifetime is automatically applied to the output. |
| 3 | If the first input is &self / &mut self, that lifetime is automatically applied to the output. |
If the output lifetime cannot be determined by these three rules, the compiler requires an explicit annotation.
5) Special Lifetime 'static
'static represents a reference that lives until the program ends. The type of string literals ("hello") is &'static str.
Examples
Example 1 — ex01_longest.rs: Explicit Lifetime
//Example 9-1: Function with explicit lifetime 'a
//
//The return lifetime of a function that returns one of two slices
//must be "as long as both are alive."
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let s1 = String::from("long");
let s2 = String::from("short");
let result = longest(&s1, &s2);
println!("Long one: {result}");
//When the scopes of the two inputs differ
let s_outer = String::from("outer scope");
{
let s_inner = String::from("inner scope-long");
let r = longest(&s_outer, &s_inner);
println!("Long one (inside scope): {r}");
//r is also valid within the lifetime of s_inner
}
}
Example 2 — ex02_struct.rs: Struct Holding a Reference
//Example 9-2: Structures with References and Lifetimes
//
//Structures that have references in their fields require lifetime parameters.
//A structure instance cannot live longer than the values its fields point to.
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn announce(&self, prefix: &str) -> &str {
println!("[{prefix}] Excerpt preview: {}", self.part);
self.part
}
}
fn main() {
let article = String::from("In Rust, a lifetime indicates the period during which a reference is valid.");
let first_sentence = article.split('.').next().expect("No sentence");
let ex = Excerpt { part: first_sentence };
let kept = ex.announce("INFO");
println!("kept = {kept}");
//While article is alive, both ex.part / kept are valid
}
Example 3 — ex03_elision.rs: Omission Rules
//Example 3 of Chapter 9: Lifetime Elision
//
//Frequently appearing patterns are automatically filled in by the compiler.
//Rules:
//1) Each reference input is assigned its own lifetime.
//2) If there is exactly one input, that lifetime is applied to the output.
//3) If the first argument of a method is &self / &mut self, that lifetime is applied to the output.
//(1) Can be omitted — one input reference → applied directly to output
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
//(2) Cannot be omitted — two inputs, so it is unclear which lifetime should be applied to output
//Explicit specification is needed.
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
//(3) Method — &self lifetime is automatically applied
struct Holder<'a> { value: &'a str }
impl<'a> Holder<'a> {
fn value(&self) -> &str { self.value } //Lifetime omitted
}
fn main() {
let s = String::from("Rust language is fast");
println!("first_word = {}", first_word(&s));
println!("longer = {}", longer("aaa", "bb"));
let h = Holder { value: &s };
println!("holder = {}", h.value());
}
Common Mistakes
Q. Does a lifetime extend the lifespan of a value?
A. No. A lifetime is just a label that tells the compiler the already determined lifespan. Declaring it does not actually increase or decrease the lifespan.
Q. Does 'a always have to be 'a?"
A. It's conventional. It's also good to give meaningful names like 'static, 'long, 'ctx. However, in simple cases, just a single alphabet is enough.
Q. It's cumbersome to write <'a> for every function.
A. If there is only one input reference like 8 cases of first_word, it is automatically handled by the omission rule. You only need to specify it where it is really necessary.
Q. Can I overuse 'static?
A. No. If forced with 'static, the caller can only pass 'static data (mostly string literals), which greatly reduces the generality of the function. Usually, a generic lifetime like 'a is used.
Summary
- A lifetime is a label indicating the valid period of a reference — it prevents dangling references.
- Specify with
<'a>and&'a Tin the function signature. - Structs with reference fields must have lifetime parameters.
- In the case of a single input reference like
&self, it is automatically filled by the omission rule.
Try it yourself
Solve the assignment below. The reference solution is shown below.