The references that need no names
You have been writing functions that take and return references for two chapters now, and you have never once typed a lifetime. That was not luck. In III.03 you learned that a reference is a borrow that must not outlive the value it points at, and the compiler held you to that rule without ever asking you to write anything down. Here is a function in exactly that style. It borrows a string and returns a borrowed view of its first word, and there is no 'a anywhere. Run it:
// Most functions that take and return references never mention a lifetime.
// The compiler fills them in for you. This one borrows a string and hands back
// a borrowed view of its first word, and not a single 'a appears.
fn first_word(s: &str) -> &str {
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
fn main() {
let sentence = String::from("borrow checker approves");
let word = first_word(&sentence);
println!("first word: {word}");
}It prints first word: borrow. The return type is &str, a borrowed string slice, the same kind of view you met in III.04. The borrow it hands back points into the very string you passed in, and the compiler knows that, even though the signature does not spell it out. The reason it does not need to is the subject of this chapter: a set of rules, called elision, that let you leave the lifetime unwritten whenever the answer is obvious.
So why have a chapter about something you can usually omit? Because usually is not always. There is one common shape where the answer is not obvious, and the compiler stops and asks you to name the region yourself. When that happens, the error message looks like a wall of punctuation, and the goal of this chapter is to make that wall read like an ordinary English sentence instead. We start by walking into the wall on purpose.
A lifetime is the span of code for which a reference is valid: from the point the borrow is created to the last point it is used. It is not a runtime thing you can print or measure; it is a compile-time region the borrow checker reasons about. Naming a lifetime, which is all 'a does, just gives one of those regions a label so a signature can talk about it.
The function that forces your hand
Here is the one shape that elision cannot resolve, and it is worth meeting as a real compiler error rather than a description. The task is simple: take two string slices and return the longer of the two. The body is obvious. The signature is the problem. Run this exactly as written and read what the compiler says:
// This is the shape that forces you to name a lifetime, and here it is broken
// on purpose. longest returns a reference, but the body could hand back either
// argument, so the compiler cannot guess how long the result stays valid.
// Run it as-is to read the real E0106 error, then fix it by writing <'a>.
fn longest(a: &str, b: &str) -> &str {
if a.len() >= b.len() {
a
} else {
b
}
}
fn main() {
let winner = longest("kilometre", "mile");
println!("longer word: {winner}");
}It does not run; it fails to compile with error E0106, missing lifetime specifier. Read the help text, because it states the problem perfectly: the function's return type contains a borrowed value, but the signature does not say whether it is borrowed from a or from b. That is the whole difficulty. The returned &str borrows from one of the inputs, but which one depends on a runtime comparison the compiler cannot see at the signature.
Stop and notice why this matters for safety, because it is the same concern from III.03 wearing a new hat. The caller will hold onto whatever longest returns. To know whether that result is still valid later, the caller needs to know which input it borrows from, so it can keep that input alive long enough. A function that returns a borrow without saying where the borrow comes from is a function the borrow checker cannot reason about. So it refuses to guess, and asks you to state the relationship.
It is tempting to stare at the if/else and hunt for the bug there. There is none. The body is correct Rust. E0106 is purely a complaint about the signature: the return type promises a borrow, and the signature has not said which input that borrow is tied to. The fix lives in the angle brackets, not in the function body.
Naming the region with 'a
The fix is a single piece of new syntax: a generic lifetime parameter. You declare it in angle brackets after the function name, written with a leading apostrophe and conventionally a single letter: 'a, read aloud as “tick-a” or just “lifetime a”. Then you attach that name to each reference you want to relate, writing &'a str instead of plain &str. Here is longest with the names filled in. Run it:
// The fix is one generic lifetime parameter, 'a. It says: the result borrows
// from the same region as both inputs, so it is valid only while BOTH are.
// 'a does not change how long anything lives; it states a relationship the
// compiler then checks at every call site.
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() {
a
} else {
b
}
}
fn main() {
let long = String::from("kilometre");
let short = String::from("mile");
let winner = longest(&long, &short);
println!("longer word: {winner}");
}It compiles and prints longer word: kilometre. Read the new signature as a sentence: fn longest<'a>(a: &'a str, b: &'a str) -> &'a str says “for some region 'a, this function takes two string slices that both live at least as long as 'a, and returns a slice that also lives at least as long as 'a.” In plain terms: the result borrows from the same region as bothinputs, so it is valid only for as long as both inputs are valid. The mystery of which input it came from is gone; the answer is “either, so treat it as tied to the shorter-lived of the two.”
The single most important thing to absorb here, and the place every newcomer goes wrong, is this: writing 'a does not change how long anything lives. It does not extend a borrow, it does not keep a value alive longer, it does not allocate. It is a label that states a relationship between the lifetimes that already exist, so the compiler has enough information to check that relationship at every call site. You are describing the world, not bending it.
Think of 'a as an adjective the signature applies to a reference, the way a type is: &'a str means “a string slice borrowing from region 'a.” Two references marked 'a are being declared to share a region. Just as writing : i32 does not make a value into an integer (it asserts that it already is one), writing 'a does not make a borrow last longer; it asserts a relationship the compiler then verifies.
Why most signatures stay bare: the three elision rules
If longest needs a lifetime but first_word from section 01 does not, there must be a rule deciding which is which. There is, and it is mechanical. The compiler applies three lifetime elision rules in order, and if they pin down every lifetime in the signature, you may omit the names. If anything is left ambiguous after all three, the compiler stops and asks, which is exactly what happened with longest. The rules, in the order the compiler tries them:
Rule one. Each reference in the parameters gets its own distinct lifetime. One & input gets one lifetime, two inputs get two separate lifetimes, and so on. This is the starting point, before any output is considered.
Rule two. If there is exactly one input lifetime, that one lifetime is assigned to every output reference. This is the rule that saves first_word: it has a single &str input, so the returned &str obviously borrows from it, and the compiler fills that in. No names needed.
Rule three. If there are multiple input lifetimes but one of them is &self or &mut self (the function is a method), the lifetime of self is assigned to every output reference. This is why methods that return a borrow of their own data almost never need an explicit lifetime, even with several arguments.
Now you can see precisely why longest failed. It has two input references, so rule one gives them two separate lifetimes. Rule two does not apply, because there is not exactly one input lifetime. Rule three does not apply, because there is no self. The output lifetime is therefore still unassigned after all three rules, and the compiler has no choice but to ask you. first_word sailed through on rule two; longest ran out of rules. That is the entire distinction.
These rules are a fixed, syntactic shorthand, applied the same way every time; the compiler is not cleverly inferring intent from the body. That is deliberate. If elision depended on what the body did, a function's public signature could silently change meaning when you edited its insides. Because the rules look only at the signature, what a caller sees is stable, and when the rules cannot decide, you are told to make the contract explicit rather than have the compiler guess.
The compiler checks lifetimes, it does not invent them
Here is the intuition that makes the whole topic click, and it is worth slowing down for. The borrow checker does not assign lifetimes to your values; your code already determines how long every value lives, by where you create it and where it goes out of scope. What the names in a signature do is let the compiler check that the borrows you wrote are consistent with those facts. Lifetimes are a description the compiler verifies, never a force it applies. This program makes the point concrete. Run it:
// A lifetime describes a region, it does not extend one. Here both borrows are
// alive for the whole println!, so the call is fine. The commented block shows
// what the checker rejects: a result that would outlive one of its sources.
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let outer = String::from("the long-lived string");
let winner;
{
let inner = String::from("short");
winner = longest(&outer, &inner);
// Used here, while inner is still alive: this is valid.
println!("inside the block: {winner}");
}
// Move the println! down here, past the end of inner's scope, and the
// compiler refuses (E0597): winner might borrow inner, which is now gone.
println!("outer alone is still fine: {outer}");
}It prints both lines, because every borrow is used while its source is still alive. The result of longest(&outer, &inner) is tied, by the 'a in the signature, to the shorter of the two regions, which is inner's. And the only place that result is used, the println! inside the block, sits while inner is still in scope. The contract holds, so the program runs.
Now do the experiment described in the comment: move the winner println! down below the closing brace, past the end of inner's scope. The compiler rejects it with E0597, “innerdoes not live long enough.” Nothing about the function changed; what changed is that you tried to use a borrow after its region had ended, and the 'a in the signature is precisely the fact that let the compiler catch it. The lifetime did not shorten inner; it described a relationship, and your edit violated it.
Scopes and ownership decide how long values live. Lifetime annotations decide nothing of the sort; they state how borrows relate to those scopes, and the borrow checker confirms the relation is sound. If you ever feel like you are “fighting the lifetime system,” you are usually fighting a real ordering problem in the code: a borrow that genuinely would outlive its source. The annotation just surfaced it early.
Structs that hold references carry a lifetime
So far lifetimes have lived on functions. They show up on data too, and for the same reason. If you put a reference inside a struct, an instance of that struct is now a borrow wearing a different shape: it must not outlive the data its field points at. Rust makes you state that by giving the struct a lifetime parameter, declared in the same angle-bracket position as a generic type. Run this:
// A struct that stores a reference must carry a lifetime parameter. It says:
// an instance of this struct may not outlive the data its field points at.
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
// Elision applies to methods too: the returned &str borrows from &self.
fn shout(&self) -> String {
format!("{}!", self.part.to_uppercase())
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("no period found");
let excerpt = Excerpt { part: first_sentence };
println!("{}", excerpt.shout());
println!("borrowed slice: {:?}", excerpt.part);
}struct Excerpt<'a> { part: &'a str } reads “an Excerpt holds a string slice borrowing from some region 'a, and the Excerpt itself may not outlive that region.” In main, the excerpt borrows a slice of novel, and because novel lives for the rest of the function, the borrow is sound and both lines print. Try, as a thought experiment, dropping novel before the last println!: the lifetime parameter is what would let the compiler reject that, because the Excerpt would be pointing at freed text.
Notice the impl<'a> Excerpt<'a> line. When a type has a lifetime parameter, its impl block declares and carries that same parameter, just as it would for a generic type. And notice what the shout method did not need: any lifetime annotation of its own. It takes &self and returns an owned String, so there is no output borrow to relate, and rule three would have handled it even if there were.
A struct field of type &'a str does not copy the string into the struct; it stores a borrow, and the 'aties the struct's own life to the borrowed data. If you want a struct that owns its text with no such leash, store an owned String instead, and the struct needs no lifetime at all. The lifetime parameter is the price, and the proof, of holding a reference rather than the thing itself.
The 'static lifetime, and where string literals live
There is one lifetime name that is not a generic parameter you introduce, but a specific, built-in one: 'static. It names the longest possible region, the entire duration of the running program. A reference of type &'static T is one the compiler can prove is valid for as long as the program runs, so you may hold it anywhere without worrying about an owner going out of scope. The most common source of 'static borrows is one you have used since chapter one without knowing its name. Run this:
// 'static is the lifetime that lasts the whole program. Every string literal
// has it, because the text is baked into the binary and never freed. So a
// &'static str is a borrow you can hold onto forever without any owner nearby.
fn banner() -> &'static str {
"Mastering Rust"
}
fn main() {
let s: &'static str = "compiled into the binary";
println!("{s}");
println!("{}", banner());
// A literal's borrow is valid for the entire run, so this is fine even
// though banner() returns a reference with no input to borrow from.
let kept = banner();
println!("still here: {kept}");
}Every string literal in Rust, every "text in quotes", has the type &'static str. The reason is concrete: the bytes of the literal are baked into the compiled binary and are never allocated or freed at runtime, so a borrow of them is trivially valid for the whole run. That is why banner can return &'static str with no input to borrow from; the data it points at outlives every possible caller. It is also why, back in section 01 of I.02, a function could return Option<&'static str> for a hardcoded name with no owner in sight.
One honest caution, because 'staticis a name people reach for too quickly. Seeing it in an error message does not mean “add 'staticto make this compile.” It usually means the compiler has worked out that some borrow would have to live for the whole program for your code to be sound, which is a signal that the real lifetimes do not line up, not an instruction to slap the longest lifetime on and move on. Reserve 'static for data that genuinely lives forever, such as literals and a few constants.
'staticmeans “valid for the entire program.” String literals get it for free because they are part of the binary. It is the longest lifetime there is, not the one to reach for whenever the borrow checker complains; a borrow that does not actually live that long must not be labelled as if it did.
Two regions, two names
One 'a on everything is the common case, but it is not the only shape. Sometimes a function takes two references whose lifetimes are genuinely unrelated, and the result borrows from only one of them. If you label both inputs 'a, you tie them together needlessly, forcing the caller to keep the unrelated one alive longer than it has to. The honest signature gives each input its own name. Run this:
// When two inputs have unrelated lifetimes and the result borrows from only
// one of them, give them separate names so you do not over-constrain the other.
// Here the result borrows from text; pattern is only read, so it gets its own
// 'b that the result never depends on.
fn before<'a, 'b>(text: &'a str, pattern: &'b str) -> &'a str {
match text.find(pattern) {
Some(i) => &text[..i],
None => text,
}
}
fn main() {
let line = String::from("name=value");
// pattern is a temporary literal with a much shorter life than line.
let key = before(&line, "=");
println!("key is {key:?}");
}before returns the slice of text up to the first occurrence of pattern. The result borrows from text and never from pattern: once find has located the index, pattern is no longer needed. The signature fn before<'a, 'b>(text: &'a str, pattern: &'b str) -> &'a str says exactly that: the return shares text's region 'a, and pattern's region 'b is independent. In main, the literal "=" can be the shortest-lived thing in the program and it still works, because the result was never tied to it.
The guidance is simple: use one shared lifetime when the result may borrow from any of the inputs (like longest), and separate lifetimes when each input plays a distinct role and the result depends on only some of them (like before). Over-sharing lifetimes is not unsafe, but it makes a function harder to call than it needs to be, so name only the relationships that truly exist.
Just as a generic type can carry a trait bound, a generic type can carry a lifetime bound, written things like T: 'a (“every reference inside T lives at least as long as 'a”). You will meet that where lifetimes combine with generics and traits, in III.09 and III.10. This chapter is the foundation those build on; the syntax you have just learned is the same syntax that shows up there.
Read it again
Scroll back to the broken longestin section 02 and read its error one more time. It is no longer a wall. “The signature does not say whether the value is borrowed from a or b” is now a precise, sensible request: tell me how the output borrow relates to the inputs, because elision's three rules could not work it out. You answer with one generic lifetime parameter, 'a, which states a relationship rather than inventing one, and the compiler checks the rest.
Everything in this chapter is one idea seen from several angles. References that need no names get their lifetimes from the elision rules. The one function that forces your hand does so because no rule covers two inputs and one output borrow. A struct that holds a reference carries a lifetime for the same reason a function does: a borrow must not outlive its source. And 'static is simply the region that lasts the whole program, the one string literals have for free.
A lifetime names the region for which a reference is valid. 'a in a signature states how borrows relate; it never changes how long anything lives. Elision lets you omit the names whenever its three rules pin them down, and you write them explicitly when they cannot. The compiler is always checking the relationships you describe, never inventing them.
The whole chapter in six lines
- A lifetime is a region of code for which a reference is valid;
'ain a signature is just a name for one of those regions. - A lifetime annotation describes how borrows relate, it does not change how long any value lives; the compiler checks the relationship, it does not invent it.
- The three elision rules (one lifetime per input reference; one input lifetime flows to the output;
self's lifetime flows to the output) let most signatures stay bare. - A function genuinely needs an explicit
'awhen it returns a borrow and the rules cannot say which input it comes from, the longest-of-two shape. - A struct that stores a referencecarries a lifetime parameter, tying the struct's own life to the borrowed data.
'staticis the region that lasts the whole program; every string literal has it, because its bytes live in the binary.