The compiler sees a reference and demands a promise
You write a function to pick the longer of two strings. Copying strings is wasteful, so you pass references. The result is just one of the inputs, so you return a reference. The code looks logical. You hit compile. The compiler rejects you with E0106: missing lifetime specifier.
You didn't forget a semicolon. You didn't mismatch types. You just forgot to tell the compiler how long the output reference stays valid. Rust refuses to guess. When a function returns a reference, the compiler needs to know exactly which input reference the output is tied to. Is it valid as long as the first argument? The second? Both? If you don't say, the compiler assumes you might be returning a reference to memory that vanishes the moment the function returns. That's a dangling pointer, and Rust won't allow it.
E0106 is the compiler's way of saying, "I see a reference here, but I don't know its duration. Give me a lifetime annotation so I can verify safety."
Lifetimes are leases, not timers
A lifetime is a promise about how long a reference remains valid. It is not a timer. It does not measure seconds or milliseconds. It measures scope. A lifetime guarantees that the data behind a reference exists for at least as long as the reference is used.
Think of a lifetime like a library book lease. When you check out a book, the library stamps a due date. That date isn't a countdown; it's a boundary. You can return the book early, but you can't keep it past the due date. If you lend the book to a friend, the friend's lease must end before or at the same time as yours. The library tracks these leases to ensure books don't disappear while someone is still reading them.
Rust tracks lifetimes to ensure references don't point to garbage memory. When you annotate a reference with a lifetime, you're telling the compiler, "This reference is valid for duration 'a." The compiler then checks every use of that reference to ensure it never outlives 'a. If it does, the code won't compile.
Lifetimes are a compile-time fiction. They vanish in the generated machine code. Adding 'a costs zero bytes and zero cycles. It is purely a static analysis tool. The compiler uses lifetimes to prove safety, then throws the annotations away. You are writing proofs, not runtime logic.
The minimal fix: explicit lifetime parameters
The standard fix for E0106 is to introduce a lifetime parameter and apply it to the relevant references. Lifetime parameters look like generic type parameters, but they start with an apostrophe.
/// Returns the longer of two string slices.
/// The lifetime 'a ties the output to both inputs.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// 'a ensures the returned reference lives as long as both x and y.
if x.len() > y.len() {
x
} else {
y
}
}
The syntax breaks down into three parts. The <'a> after the function name declares a lifetime parameter named 'a. The &'a str on the arguments and return type applies that parameter. Every reference tagged with 'a must live at least as long as 'a. The compiler enforces this at the call site.
When you call longest, the compiler calculates the intersection of the input lifetimes. If x is valid for scope A and y is valid for scope B, the return value is valid for the smaller of the two scopes. This prevents the return value from outliving either input.
Convention aside: Rustaceans usually name the first lifetime parameter 'a. If you have multiple parameters, use 'b, 'c, or descriptive names like 's for string data or 'ctx for context. Single letters are standard for function parameters. Descriptive names help when lifetimes get complex.
Write the lifetime. The compiler isn't psychic.
When the compiler guesses for you: elision rules
Rust has a set of lifetime elision rules that let you omit annotations in common cases. These rules exist to reduce boilerplate, not to hide complexity. E0106 appears when the elision rules don't apply.
The compiler applies three rules in order:
- Each input reference gets its own lifetime parameter.
- If there is exactly one input lifetime, that lifetime is assigned to all output references.
- If there are multiple input lifetimes but one of them is
&selfor&mut self, the lifetime ofselfis assigned to all output references.
If none of these rules match, the compiler gives up and emits E0106.
/// Elision works here: one input reference.
/// The compiler assigns the input lifetime to the output automatically.
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
s.len()
&s[..]
}
/// Elision fails here: two input references.
/// The compiler doesn't know which input lifetime to assign to the output.
/// This triggers E0106.
// fn longest(x: &str, y: &str) -> &str { ... }
In first_word, there is one input reference s. Rule 2 applies. The output gets the same lifetime as s. No annotation needed.
In longest, there are two inputs. Rule 2 doesn't apply. Rule 3 doesn't apply. The compiler stops. You must add explicit lifetimes.
Don't rely on elision to save you. Write the lifetimes explicitly until you understand the rules. Clarity beats brevity.
Structs and enums always need explicit lifetimes
Elision rules apply only to function signatures. Structs and enums never elide lifetimes. If a struct contains a reference, you must declare a lifetime parameter on the struct itself.
/// A document that borrows its title from external data.
/// The struct is only valid as long as the title reference is valid.
struct Document<'a> {
// 'a ties the struct's validity to the title's lifetime.
title: &'a str,
content: String,
}
impl<'a> Document<'a> {
/// Creates a new document with a borrowed title.
fn new(title: &'a str) -> Self {
Document {
title,
content: String::new(),
}
}
}
The <'a> on Document declares the lifetime parameter. The &'a str on title applies it. The struct itself carries the lifetime. An instance of Document<'a> is only valid while the data behind title exists. If the title data is dropped, the document becomes invalid.
The impl block also needs the lifetime parameter. impl<'a> Document<'a> tells the compiler that the methods in this block work for any lifetime 'a. Without the parameter on the impl, you get E0106 or E0107.
If you try to define struct Doc { title: &str }, the compiler rejects it with E0106. Structs cannot have bare references. Every reference in a struct must have an explicit lifetime.
Trust the borrow checker here. Structs with references are borrowing contracts. Write the contract explicitly.
Pitfalls and compiler errors
E0106 shows up in a few specific patterns. Recognizing them speeds up fixes.
Missing lifetime on impl blocks. If your struct has a lifetime, your impl block must declare it.
struct Doc<'a> {
title: &'a str,
}
// Error E0106: missing lifetime specifier.
// The compiler sees &str in methods and can't infer the lifetime.
// impl Doc {
// fn get_title(&self) -> &str { self.title }
// }
// Fix: declare the lifetime on the impl block.
impl<'a> Doc<'a> {
fn get_title(&self) -> &str {
self.title
}
}
Multiple inputs with different lifetimes. If a function takes references with different lifetimes, you need multiple parameters.
/// Compares a borrowed string with a static string.
/// 'a is for the input, 'static is for the literal.
fn compare<'a>(input: &'a str, default: &'static str) -> &'a str {
if input.is_empty() {
// Error E0308: mismatched types.
// Can't return &'static str where &'a str is expected.
// default
input
} else {
input
}
}
Here, input has lifetime 'a. default has lifetime 'static. The return type is &'a str. Returning default fails because 'static doesn't automatically coerce to 'a in this context without careful handling. The compiler rejects the mismatch. You need to align the lifetimes or return the correct type.
Lifetimes in closures. Closures capture references and inherit lifetimes. If a closure returns a reference, the lifetime can get tangled. E0106 may appear if the closure signature is explicit and missing a specifier.
// Closure elision usually works, but explicit signatures need care.
let f = |x: &str, y: &str| -> &str { x };
// Error E0106: missing lifetime specifier.
// The compiler can't infer the output lifetime from two inputs.
Fix this by avoiding explicit closure signatures when possible, or by using a helper function with proper lifetimes. Closures are tricky with lifetimes. Keep them simple.
Lifetimes are promises. Keep them consistent.
Decision: when to use explicit lifetimes vs alternatives
Use explicit lifetime annotations when a function takes multiple references and returns a reference; the compiler cannot infer which input lifetime applies to the output. Use explicit lifetime annotations when a struct or enum contains references; elision rules do not apply to data structures. Use explicit lifetime annotations when an impl block contains methods that return references tied to the struct's lifetime; the block must declare the parameter. Use lifetime elision (omit annotations) when a function has exactly one input reference and returns a reference; the compiler applies the input lifetime to the output automatically. Use lifetime elision when a method returns a reference tied to self; the compiler assigns self's lifetime to the output. Use the 'static lifetime when the data lives for the entire duration of the program, such as string literals or global constants; this satisfies E0106 by promising the reference never expires.
Counter-intuitive but true: the more you understand lifetimes, the less you need to write them. Elision covers most simple cases. Master the explicit form first, then let the compiler handle the rest.