Beginner · Borrowing, slices and lifetimes · topic 17 of 50 · 35 min
Lifetimes
Explanation
Why lifetimes exist
Rule 2 of borrowing says a reference must never outlive the value it points to. The compiler checks this by tracking a lifetime for every reference: the region of code where it is valid. Most of the time this is invisible. You only write lifetimes when the compiler cannot work out, from a function's signature alone, how long a returned reference is valid.
let r; // ---------+-- 'a (r)
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ | x dropped here
println!("{r}"); // | r used here: x is gone!
// ---------+A function that needs an annotation
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
// error[E0106]: missing lifetime specifier
// help: this function's return type contains a borrowed value, but the
// signature does not say whether it is borrowed from `x` or `y`The fix is a lifetime parameter. It is written with an apostrophe and a short name, usually 'a, declared in angle brackets after the function name, like a generic.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}In practice, 'a becomes the shorter of the two input lifetimes. The caller may use the result only while BOTH inputs are still alive.
fn main() {
let a = String::from("Thiruvananthapuram");
let result;
{
let b = String::from("Goa");
result = longest(&a, &b);
println!("{result}"); // OK: a and b are both alive
}
// println!("{result}"); // error[E0597]: `b` does not live long enough
}Lifetime elision: why you rarely write them
Elision means "leaving out". The compiler applies three rules to fill in lifetimes for you:
- Each reference parameter gets its own lifetime.
- If there is exactly one input lifetime, it is given to all output references. That is why fn first_word(s: &str) -> &str needs no annotation.
- If there is a &self or &mut self parameter (a method), its lifetime is given to the outputs.
If the rules still leave an output lifetime unknown, as with longest (two inputs, no self), you must write it.
Structs that hold references
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let e = Excerpt { part: first };
println!("{}", e.part); // Call me Ishmael
}'static
'static is a special lifetime meaning "valid for the whole program". String literals are &'static str because they are stored inside the program file. When the compiler suggests adding 'static, it is usually a sign to return an owned String instead, not to make your data static.
Common mistakes
- Adding 'static to silence an error. It rarely fixes the real problem.
- Thinking 'a makes a value live longer. It does not; it only describes.
- Putting references in structs everywhere. Start with owned data.
- Annotating every function. Let elision work; add lifetimes only when the compiler asks.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Lifetimes · 1/8
A lifetime is the stretch of code where a reference is valid. The compiler tracks one for every reference.
Prefer a video by a teacher? Find free videos on “rust lifetimes explained for beginners elision” on YouTube ↗
In real life: Parsing a log line without copying
A monitoring tool at a hosting company reads millions of log lines a minute. Copying every field into a new String would waste memory. Instead, a LogEntry struct holds &str slices that point into the original line, with a lifetime tying the entry to the line. The compiler guarantees no entry is used after its line is freed, and nothing is copied.
struct LogEntry<'a> {
level: &'a str,
message: &'a str,
}
fn parse(line: &str) -> Option<LogEntry<'_>> {
let (level, message) = line.split_once(": ")?;
Some(LogEntry { level, message })
}
fn main() {
let line = String::from("ERROR: payment gateway timeout");
if let Some(e) = parse(&line) {
println!("[{}] {}", e.level, e.message);
}
}Practice (0/3 done)
Which of these function signatures needs an explicit lifetime annotation?
What does 'a in fn longest<'a>(x: &'a str, y: &'a str) -> &'a str do?
Write fn shorter<'a>(x: &'a str, y: &'a str) -> &'a str. Call it so that it compiles, then move one String into an inner block so the result is used after the block, read the E0597 error, and fix it. Finally, write a struct Quote<'a> { text: &'a str, by: &'a str } and print one quote.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Interview questions: ownership, borrowing and lifetimesRust careers and interviews
- Scope and storage classesC Programming (from zero to systems) · Functions and arrays
- Dependency injection and service lifetimesC# and .NET · Web APIs with ASP.NET Core and EF Core
- References: another name for a variableC++ Programming (modern C++ and DSA) · References, pointers, arrays and strings
- Functions, statements and expressionsTypes, control flow and functions
- Ownership and functionsOwnership: Rust's big idea
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is a lifetime?
Answer: The region of code where a reference is valid
Lifetimes describe where references are valid.
Do lifetime annotations change how long values live?
Answer: No, they only describe relationships between references
Annotations are checked labels, not instructions to keep data alive.
Why does fn first(s: &str) -> &str need no annotation?
Answer: Elision: with one input lifetime, the output gets it
Elision rule 2 gives the single input lifetime to the output.
What lifetime do string literals have?
Answer: 'static
Literals live inside the program binary for the whole run.
The compiler suggests adding 'static to a returned reference to a local String. What is usually the right fix?
Answer: Return the owned String instead
A local String is dropped at the end of the function; returning ownership is the clean fix.
Which statement about non-lexical lifetimes is correct?Related
Answer: Borrows last until their last use
NLL ends borrows at their last use.
From Interview questions: ownership, borrowing and lifetimes
How long does a static local variable live?Related
Which lifetime is usual for an EF Core DbContext in ASP.NET Core?Related
Answer: Scoped
AddDbContext registers it as scoped: one per request.
What is wrong with: const std::string& name() { std::string s = "Asha"; return s; }Related
Answer: It returns a reference to a local that is destroyed: a dangling reference
s dies when the function returns. Return std::string by value.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Borrowing, slices and lifetimes”. The module exam comes next.
- Module 4 exam: Borrowing, slices and lifetimes · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Structs, enums and pattern matching. 50 topics left in the course before the final exam.