Lifetimes
Reviewed & published by Brayan K
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and lifetimes are how it guarantees, at compile time, that a reference never outlives the data it points to.
Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll learn what lifetimes describe, annotate functions and structs that hold references, and understand elision and the special 'static lifetime.
What You'll Learn in This Lesson
- What a lifetime actually describes
- Annotating functions that return references
- Why lifetimes prevent dangling references
- Lifetimes on structs that hold references
- Lifetime elision and when it applies
- The special 'static lifetime
1️⃣ Lifetimes in Functions
When a function returns a reference, the compiler must know which input that reference borrows from, so it can guarantee the result doesn't outlive its source. A lifetime parameter like 'a expresses this: tying the inputs and output to the same 'a says "the result lives as long as the shorter-lived input".
// This function returns a reference. The compiler needs to know the returned
// reference won't outlive the data it points to. A LIFETIME parameter 'a says:
// "both inputs and the output share the same lifetime 'a".
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("hello there");
let s2 = String::from("hi");
// The returned reference is valid as long as BOTH s1 and s2 are alive.
let result = longest(&s1, &s2);
println!("longest: {}", result); // -> longest: hello there
// Lifetimes do not change how long values live; they only DESCRIBE the
// relationships so the compiler can prevent dangling references.
}The 'a doesn't make anything live longer — it just describes the relationship so the borrow checker can prove the returned reference is always valid.
2️⃣ Lifetimes on Structs, Elision, and 'static
A struct that holds a reference must declare a lifetime, ensuring it can't outlive the borrowed data. In many functions, though, you write no lifetimes at all thanks to elision — rules that let the compiler infer them. The special 'static lifetime means "valid for the whole program", which is the lifetime of string literals.
// A struct that HOLDS a reference must declare a lifetime, so the struct
// cannot outlive the data it borrows.
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn announce(&self) -> &str {
self.part
}
}
fn first_sentence(text: &str) -> &str {
// Lifetime ELISION: the compiler infers lifetimes here, so we don't write 'a.
match text.find('.') {
Some(i) => &text[..i],
None => text,
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let sentence = first_sentence(&novel);
let e = Excerpt { part: sentence };
println!("Excerpt: {}", e.announce()); // -> Excerpt: Call me Ishmael
// 'static is a special lifetime: data that lives for the whole program.
let forever: &'static str = "I live for the entire program";
println!("{}", forever);
}Note first_sentence needed no explicit lifetime — elision handled the single-reference case. The Excerpt<'a> struct, however, holds a reference, so its lifetime had to be written.
Your turn. Fill in the blanks marked ___, then run it.
// 🎯 YOUR TURN — add the lifetime annotations, then run it.
// This returns whichever string starts first alphabetically. Both inputs and
// the output must share a lifetime. Fill in the lifetime parameter.
fn first_alpha<___>(a: &'a str, b: &'a str) -> &'a str { // 👉 declare 'a
if a < b { a } else { b }
}
fn main() {
let x = String::from("apple");
let y = String::from("banana");
let result = first_alpha(&x, &y);
println!("first: {}", result); // expected: first: apple
}
// ✅ Expected output:
// first: appleMini-Challenge: Longest of Three
Extend the classic example to three inputs, annotating the shared lifetime. Run it with cargo run.
// 🎯 MINI-CHALLENGE: a longest-of-three.
//
// 1. Write fn longest3<'a>(a: &'a str, b: &'a str, c: &'a str) -> &'a str
// that returns the longest of the three strings (by .len()).
// Hint: compare two at a time.
// 2. In main, call it on "rust", "is", and "awesome".
// 3. Print "Longest: awesome".
//
// ✅ Example output:
// Longest: awesome
fn main() {
// your code here
}Common Errors (and the fix)
- "missing lifetime specifier" — A function returns a reference but the compiler can't infer its source. Add a lifetime: fn f<'a>(x: &'a str) -> &'a str.
- "borrowed value does not live long enough" — A reference would outlive its data. Restructure so the data lives at least as long as the reference.
- "`x` does not live long enough" — Often the fix is to extend the scope of the borrowed value or return an owned value instead.
- Overusing 'static — Slapping 'static on to silence errors rarely helps; fix the underlying scope problem instead.
Pro Tips
- 💡 Most code needs no annotations: elision handles the common cases — you'll only annotate occasionally.
- 💡 Return owned data to dodge lifetimes: returning a String instead of a &str sidesteps the whole question.
- 💡 Lifetimes are descriptions: they never change runtime behaviour — they only let the compiler verify safety.
📋 Quick Reference — Lifetimes
| Goal | Rust Syntax |
|---|---|
| Declare on fn | fn f<'a>(x: &'a str) |
| Tie input to output | (x: &'a str) -> &'a str |
| Struct with reference | struct E<'a> { p: &'a str } |
| Impl with lifetime | impl<'a> E<'a> { } |
| Whole-program | &'static str |
🎉 Lesson Complete!
- ✅ A lifetime describes how long a reference is valid
- ✅ Annotations like 'a relate input and output references; they don't change runtime
- ✅ Structs holding references must declare a lifetime
- ✅ Elision infers lifetimes in common cases; 'static lasts the whole program
Practice quiz
What does a lifetime describe?
- How fast a function runs
- The size of a value
- How long a reference is valid
- How much memory is used
Answer: How long a reference is valid. A lifetime describes the scope during which a reference stays valid.
Do lifetime annotations change how long values actually live?
- No, they only describe relationships
- Yes, they extend life
- Yes, they shorten life
- Only for 'static
Answer: No, they only describe relationships. Annotations describe relationships so the borrow checker can verify safety.
How is a lifetime parameter written?
- <T>
- <life>
- ['a]
- <'a>
Answer: <'a>. A lifetime parameter looks like 'a, declared in angle brackets.
When does the compiler most often require a lifetime annotation?
- When printing a value
- When a function returns a reference from multiple inputs
- When using mut
- When calling a macro
Answer: When a function returns a reference from multiple inputs. It needs to know which input a returned reference borrows from.
What does fn longest<'a>(x: &'a str, y: &'a str) -> &'a str express?
- The output lives as long as the shorter-lived input
- The output outlives both inputs
- x is copied
- The strings are mutable
Answer: The output lives as long as the shorter-lived input. Tying inputs and output to 'a means the result is valid while both inputs are.
A struct that holds a reference must:
- Be marked mut
- Use a HashMap
- Declare a lifetime
- Implement Display
Answer: Declare a lifetime. Storing a reference requires a lifetime so the struct can't outlive the data.
What is lifetime elision?
- Deleting references
- Rules letting the compiler infer lifetimes in common cases
- A runtime check
- A kind of macro
Answer: Rules letting the compiler infer lifetimes in common cases. Elision rules infer lifetimes so you often write none at all.
What does the 'static lifetime mean?
- Valid for one function
- Never valid
- A mutable reference
- Valid for the entire program
Answer: Valid for the entire program. 'static means the reference is valid for the whole program duration.
What type do string literals have?
- String
- &'static str
- &mut str
- char
Answer: &'static str. String literals are &'static str because their text lives in the binary.
Is reaching for 'static usually the right fix for a lifetime error?
- Yes, always
- Only with mut
- No, it rarely fixes the real problem
- Only for structs
Answer: No, it rarely fixes the real problem. 'static rarely helps; the real issue is usually a value not living long enough.
Continue this course
- Previous: Traits
- Next: Closures — Anonymous functions that capture their environment — Fn, FnMut, FnOnce
- Quick reference: Rust cheat sheet
Frequently asked questions
What is a lifetime in Rust?
A lifetime is a label that describes how long a reference is valid — that is, the scope during which the data it points to is guaranteed to exist. Every reference has a lifetime; most of the time the compiler figures it out for you. Lifetime annotations like 'a are not commands that change how long anything lives; they are descriptions that tell the compiler how the lifetimes of several references relate, so it can guarantee no reference ever outlives the data it points to and become dangling.
Why does the compiler sometimes ask me to add a lifetime?
Most often it happens when a function returns a reference and the compiler cannot tell which input the output borrows from. If a function takes two string references and returns one, the compiler needs to know whether the result borrows from the first, the second, or both, so it can ensure the returned reference stays valid. Annotating the parameters and return with a shared lifetime, like 'a, answers that question. You also need lifetimes when a struct stores a reference.
What is lifetime elision?
Lifetime elision is a set of rules that let the compiler infer lifetimes in common cases so you do not have to write them. For example, a function taking a single reference and returning a reference has its lifetimes inferred automatically, which is why many functions that work with &str need no explicit 'a. The annotations only become necessary when the rules are ambiguous — typically with multiple reference parameters where the relationship to the output is unclear. Elision is why beginners can write lots of borrowing code before ever seeing a lifetime annotation.
What does the 'static lifetime mean?
The 'static lifetime means a reference can be valid for the entire duration of the program. String literals have type &'static str because their text is baked into the program's binary and never goes away. You will also see 'static as a trait bound in some advanced situations, where it means a type contains no shorter-lived references. Be careful not to reach for 'static just to silence a lifetime error — it is rarely the right fix and usually signals that the real problem is a value not living long enough.