TL;DR: Lifetimes don't extend how long data lives — they just label the connection between borrowed inputs and outputs so Rust can verify nothing outlives what it borrowed from. Full breakdown below 👇
You've probably seen this error before:
error[E0106]: missing lifetime specifier
And your first reaction was probably: "I just wrote a function. Why is the compiler talking to me about time travel?"
Stay with me. By the end of this post, you'll see that lifetimes aren't some advanced wizardry bolted onto Rust. They're Rust doing you a favor — just in a language it hasn't learned to say nicely yet.
A real-life example first (no code, just think about this)
Imagine you borrow a book from a friend.
You can read it, highlight it, even lend it to someone else for a bit. But there's one rule you never break: you can't still be holding that book after your friend has thrown it away.
That would be absurd, right? The book doesn't exist anymore. Holding onto it would mean holding onto nothing — a book-shaped hole in your hands.
That's it. That's the entire idea behind lifetimes, before we even touch Rust.
- Your friend owns the book. They decide when it gets thrown away.
- You're just borrowing it. You have to give it back — or at least, stop using it — before it's gone.
- Rust's job is to make sure you never end up holding a thrown-away book.
Keep this in your head. We're not leaving this analogy for the rest of the post.
Let's bring this into code — but with a simple example
Forget the textbook example of comparing two strings. Let's use something more relatable: a function that returns whichever name is longer, out of two names you give it.
fn longer_name(a: &str, b: &str) -> &str {
if a.len() > b.len() {
a
} else {
b
}
}
Looks completely reasonable. You give it two borrowed strings, it hands one back. Try to compile it, though, and Rust stops you:
error[E0106]: missing lifetime specifier
This is the moment that actually matters. Not the fix — this exact confusion. I'm not creating anything. I'm not even keeping the data. I'm just returning one of the two things you gave me. Why does Rust care?
Here's the plain-language reason why
Go back to the borrowed book. Now imagine your friend hands you two books and says, "give me back whichever one is thicker."
Simple enough for you to do. But now imagine someone standing outside the room, watching you walk out holding a book. They have no idea which friend that book belongs to, or how long they're allowed to keep it before its owner wants it back or throws it away.
That's exactly the problem Rust has with longer_name. When it reads:
fn longer_name(a: &str, b: &str) -> &str
Rust sees: "I'm handed two borrowed things, and I'm handing back one borrowed thing." But it has no way of knowing which one you're handing back — a's book, or b's book. And since a and b might be thrown away (go out of scope) at different times, Rust genuinely cannot guarantee the thing you return will still exist by the time someone tries to use it.
Rust isn't confused about your code. Rust is refusing to guess. It won't let you hold onto a book that might already be in the trash — and it won't compile your program until you tell it exactly whose book is being borrowed.
So what do we actually write?
This is where lifetimes come in — and here's the part that made it click for me: a lifetime annotation isn't a value, a type, or a special power. It's a label.
It's you, standing in that room, saying to the person watching: "This exact book — I'm holding it for exactly as long as my friend allows. Not a second longer."
In code, that label is written as 'a:
fn longer_name<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() {
a
} else {
b
}
}
Read this out loud in plain English: "Whatever I return lives for exactly as long as the shorter-lived of a and b allows."
That's the whole sentence. You're not telling Rust how long something lives — Rust already knows that. You're telling Rust that the thing you return is tied to the same borrowing rules as what came in. You're just labeling the connection.
This compiles, because now the watcher outside the room knows exactly whose book you're holding — and can confirm that friend hasn't thrown it away yet.
The part almost nobody explains clearly
Here's the sentence that finally made lifetimes make sense to me:
Lifetimes don't make anything live longer. They only describe how long things already live, so Rust can check your work.
You're not controlling time. You're labeling it. It's the difference between a clock and a calendar reminder — the reminder doesn't change when the event happens, it just makes sure you don't miss it.
This is also why lifetimes disappear from so much everyday Rust code. If a function only borrows data and returns something unrelated to it (like a bool, or an owned String it built itself), there's no connection to label — so there's nothing to write.
You only write a lifetime when you're handing back a borrowed book, and Rust needs to know which friend to check back with.
Quick side-by-side, in plain words
| Owning a book | Borrowing a book (&) |
Borrowing with a labeled lifetime (&'a) |
|
|---|---|---|---|
| Real-life version | It's yours. Keep it as long as you want. | You have it temporarily, must return before it's thrown away. | You have it temporarily, and you've told everyone exactly whose copy it is. |
| In Rust |
String, Vec<T>, etc. |
&str, &T
|
&'a str, &'a T
|
| Who decides when it's gone | You do. | The owner does. | The owner does — Rust just tracks and confirms it. |
| What the compiler checks | Nothing extra. | That you don't use it after it's gone. | That the connection between input and output borrows is honored. |
The actual error, decoded
Next time you see one of these, here's what Rust is really saying:
-
missing lifetime specifier→ "You're returning (or storing) a borrowed value, and I can't tell whose rules it's supposed to follow. Label it." -
borrowed value does not live long enough→ "You're trying to keep the book after your friend already threw it away." -
cannot return value referencing local variable→ "You're trying to hand back a book that only existed inside this room — it won't exist once you walk out."
None of these are Rust being difficult. Every single one is Rust catching a bug that, in another language, would've quietly turned into a crash — or worse, a security hole — at runtime.
Three myths about lifetimes, cleared up
Myth 1: "Lifetimes make my data live longer."
No. They don't extend anything. They only describe how long something already lives, so Rust can verify your code respects it.
Myth 2: "I need to write 'a everywhere."
No. Rust infers lifetimes automatically in the vast majority of code (this is called lifetime elision). You only write them explicitly when there's a genuine ambiguity — like our longer_name example, where two different borrows are competing.
Myth 3: "This is a uniquely Rust problem, other languages don't have this."
Other languages have this exact problem — dangling pointers, use-after-free bugs. They just don't tell you about it at compile time. They let it become a runtime crash instead. Rust just refuses to let it slide.
So when do lifetimes actually show up?
Ask yourself one simple question:
"Am I handing back (or storing) something I only borrowed — and is there more than one place it could have come from?"
- If yes — like
longer_name, or a struct that holds a borrowed reference — Rust needs a label to know which "friend" to check back with. - If no — if you're returning something you built yourself, or something unrelated to what was borrowed — there's nothing to label, and you'll never see
'ain your code.
Why this is genuinely "the heart of Rust"
This is the same promise you've already seen in ownership and in dispatch: Rust refuses to let your program lie about what's happening in memory. Lifetimes are how that promise extends to borrowed data specifically — Rust won't let you hold a reference to something that might not exist anymore, and it catches that mistake before your program ever runs, not after it crashes in production.
You're not fighting the compiler. You're standing in a room, holding a borrowed book, and a very honest friend is simply asking: "are you sure that book still exists?"
Once you can answer that question, you already understand lifetimes.
If this helped, I write a lot more Rust content like this — breaking down confusing concepts using plain, real-life analogies instead of jargon. Follow along for more.
Top comments (0)