When the compiler asks "but for how long?"
You're writing a struct that holds a borrowed string slice. Maybe it's a parser keeping a reference to the source it was parsing, or a cache entry pointing at some shared config text. You add a method that returns part of that borrowed data, hit save, and the compiler stops you cold:
error[E0106]: missing lifetime specifier
Welcome to one of the more confusing-looking parts of Rust. Lifetimes on impl blocks always feel a bit ceremonial the first time, with apostrophes scattered everywhere like punctuation gone wild. Once you see what the compiler is actually asking, it stops feeling like noise.
What lifetimes are really for
A lifetime is a label. Nothing more. It doesn't change what the code does at runtime. It exists purely so the compiler can connect "this reference came from over there" with "that data must still be alive at this point."
Imagine library books with colored stickers. A red sticker says "this slot in the catalog refers to the red book." If someone returns the red book, the librarian needs to know that the red catalog entry is now stale and shouldn't be handed out. Rust's lifetimes do the same job: every reference gets tagged, and the compiler tracks which tag must outlive which.
When a struct holds references inside it, the struct itself needs a lifetime parameter. That parameter says "I borrow from somewhere, and that somewhere must live at least as long as I do." Once you've put that parameter on the struct, you have to thread it through any impl block you write for that struct.
The minimal pattern
Here's the smallest example that has all the moving pieces.
// A struct that borrows part of a string. The 'a parameter says
// "this struct doesn't outlive whatever string my `part` points into."
struct Excerpt<'a> {
part: &'a str,
}
// The impl block has to declare 'a too, and apply it to Excerpt.
// Without this, the compiler doesn't know what 'a means in the methods below.
impl<'a> Excerpt<'a> {
// Returns a borrow tied to the same 'a. Whoever uses the returned
// string must hold an Excerpt that's still alive.
fn part(&self) -> &str {
self.part
}
// Same thing, but written explicitly. This and the version above
// mean the same thing because of lifetime elision rules.
fn part_explicit<'b>(&'b self) -> &'b str {
self.part
}
}
The two-step dance is the part newcomers miss. You need <'a> on the struct, AND you need <'a> on the impl block, AND you need Excerpt<'a> after impl<'a>. All three. They're not the same 'a reused; the second 'a is a fresh declaration on the impl block, and the third use says "this impl applies for any choice of 'a."
If that feels like over-explaining, here's why it has to be that way. impl<'a> Excerpt<'a> is exactly like a generic function: you're saying "for every possible lifetime 'a, here's how Excerpt<'a> behaves." If you wrote impl Excerpt<'a> without declaring 'a, the compiler would ask "where does that 'a come from?" Same as if you wrote fn foo() -> T without saying <T> somewhere.
What lifetime elision is actually doing
You'll write a lot of methods like this and never type a single apostrophe:
impl<'a> Excerpt<'a> {
// No lifetime annotations on the parameters or return type.
// The compiler fills them in for you.
fn first_word(&self) -> &str {
self.part.split_whitespace().next().unwrap_or("")
}
}
That's because of lifetime elision. There are three rules baked into the compiler that handle the common cases. The relevant one for methods: if &self is among the parameters, the return value's lifetime defaults to the lifetime of &self. So the example above is treated as if you wrote fn first_word<'b>(&'b self) -> &'b str. You only have to spell things out when the compiler can't figure it out, like when the return value should be tied to a different parameter, or when there's no &self and multiple references are involved.
The trick to debugging lifetime errors on methods is to write out the elided form by hand and see if it actually says what you meant. Often the answer is "no, the compiler is right, the borrow doesn't live as long as I claimed."
A more realistic example
Let's build something a learner might actually write: a tiny parser that keeps a reference to the source code it's parsing, then exposes pieces of that source through methods.
// A parser that doesn't own its source. It keeps a reference,
// so the source must outlive the parser.
struct Parser<'src> {
source: &'src str,
cursor: usize,
}
impl<'src> Parser<'src> {
// Constructors that return a Parser borrowing from the input.
fn new(source: &'src str) -> Self {
Parser { source, cursor: 0 }
}
// Returns a borrowed slice of the source. The returned &str
// is tied to 'src, the source's lifetime, NOT to &self.
// That matters: the caller can hold this slice longer than
// the parser itself.
fn rest(&self) -> &'src str {
&self.source[self.cursor..]
}
// Advances the cursor by `n` bytes. Doesn't return a borrow,
// so no lifetime annotations are needed beyond the impl header.
fn advance(&mut self, n: usize) {
self.cursor += n;
}
}
fn main() {
let text = String::from("hello world");
let token: &str;
{
let mut p = Parser::new(&text);
p.advance(6);
// The slice we get back is tied to `text`, not to `p`.
token = p.rest();
// `p` goes out of scope here.
}
// We can still use `token` because `text` is still alive.
println!("{token}");
}
The interesting bit is rest. Spelled out, its signature is fn rest<'a>(&'a self) -> &'src str. The returned slice's lifetime is 'src, not 'a. Why? Because the slice points into self.source, which itself is &'src str. We don't need the parser to be alive to keep using a slice of the original source. We only need the original source to be alive.
If we had elided that lifetime, the compiler would have applied the default rule and tied the return type to &self. That would have worked for many uses, but it would have made the example above fail: token couldn't outlive p. By spelling out &'src str, we got the more flexible answer.
Common pitfalls
You wrote impl Excerpt<'a> without the <'a> declaration. The error is:
error[E0261]: use of undeclared lifetime name `'a`
Fix: write impl<'a> Excerpt<'a>. Always introduce a lifetime where you intend to use it.
You returned a reference whose lifetime can't possibly be valid:
impl<'a> Excerpt<'a> {
fn local() -> &'a str {
let s = String::from("hi");
&s // s dies at the end of this function
}
}
The error:
error[E0515]: cannot return reference to local variable `s`
The fix is structural. Either return String (owned), or take a reference as a parameter and return a slice of that.
You over-annotated. New Rustaceans sometimes write things like fn level(&'a self) -> &'a str when both lifetimes don't actually need to be the same. That's not wrong, exactly, but it's overly restrictive. You're telling the compiler "this borrow of self must last as long as the struct itself," which is a strong claim and can prevent valid uses. Default to elision; only annotate by hand when you have a reason.
You hit "the lifetime cannot outlive the lifetime of self" (E0495 or E0521). This usually means you're trying to return a reference to something that doesn't live as long as you claimed. Walk through the chain: where does the data come from, and is it really tied to the parameter you said it was tied to?
When to use what
Plain owned types if you don't need to share. Replace &str with String, &[T] with Vec<T>. The lifetime annotation goes away because there's no borrow.
Lifetimes on structs and impl blocks when you genuinely want to avoid copying or want to reflect a lending relationship in the type system: parsers, iterators, view types, anything backed by a buffer.
Generic associated types or higher-ranked bounds when the lifetime relationships get complex enough that a single <'a> isn't expressive enough. That's beyond this article and you'll know it when you hit it.
Where to go next
Lifetimes on impls are a step on a longer path. The earlier you get comfortable with the basics, the less scary the corner cases feel.
Lifetimes and the self Parameter in Methods