You have written functions that take references. At some point you tried to return one - and Rust
stopped you with an error about lifetime specifiers. You added 'a to the signature, and either
it worked or it didn’t, and either way you were not sure why. The assumption most people carry at
this point is that 'a controls how long something lives. It does not.
The rental agreement
Imagine you are organising an event. You have rented a hall, booked from seven until midnight. Under that booking you issue guest passes. Every pass is valid for the duration of the booking and no longer. When the booking ends and the hall is handed back, every pass becomes worthless - the venue manager will not honour them. The booking agreement does not say how long the hall exists; it says how long your claim on it lasts. The passes do not extend the booking. They reference it.
Now imagine you have two potential venues - a main hall and a backup. Both are available from the start of the event, but the main hall is booked until ten and the backup runs until midnight. Which one you use depends on attendance. You issue a pass to the catering team. That pass must work regardless of which hall is chosen - which means it is only valid while both options are still open, that is, until ten. Not because of anything written on the pass, but because that is the only period where the result is guaranteed to hold.
That is what the 'a annotation does in Rust: it declares that the returned reference is valid for
exactly as long as both arguments are alive. No longer.
The date on the pass
A common misconception: 'a is a duration, and writing it tells Rust to keep something alive
longer. This model is wrong. It sticks because adding 'a sometimes makes the compiler error
disappear - and that feels like confirmation. It is a coincidence, not a reason.
fn active_label(current: &str, default: &str) -> &str {
if current.is_empty() { default } else { current }
}
error[E0106]: missing lifetime specifier
--> src/main.rs:1:46
|
1 | fn active_label(current: &str, default: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature
does not say whether it is borrowed from `current` or `default`
The compiler is not asking how long anything should live. It is asking: until when is this pass
valid? The returned &str points to data owned by one of the two inputs - but without the
annotation the compiler does not know which, and therefore cannot determine how long the pass is
valid or verify that it will not expire before it is used.
Labelling the pass
fn active_label<'a>(
current: &'a str, // main hall - until ten
default: &'a str, // backup hall - until midnight
) -> &'a str { // pass valid while both are open
if current.is_empty() { default } else { current }
}
'a does not make current or default live longer. It is a declaration: the returned reference
is valid within the intersection of their durations - while both are alive. The actual duration is
determined by the scopes at the call site.
Now the compiler can check:
fn main() {
let label;
{
let current = String::from("beta"); // main hall opens
label = active_label(¤t, "stable"); // pass issued - valid while both are open
} // main hall closed - pass expired
println!("{}", label); // pass is no longer valid
}
error[E0597]: `current` does not live long enough
--> src/main.rs:9:28
|
9 | label = active_label(¤t, "stable");
| ^^^^^^^^ borrowed value does not live long enough
10 | }
| - `current` dropped here while still borrowed
11 | println!("{}", label);
| ----- borrow later used here
The annotation made the dependency visible. The compiler found the violation. The fix is not to
change the annotation - it is to use label while both halls are still open:
fn main() {
let current = String::from("beta"); // main hall opens
let label = active_label(¤t, "stable"); // pass issued - both halls still open
println!("{}", label); // used while both are open
} // halls close - pass expires, but we're done
What you read differently now
When you see 'a in a function signature, stop reading it as a timer. Read it as a condition:
the returned reference is valid while both arguments are alive.
One more thing worth knowing: if you need to return a reference from only one specific argument,
you can give them different annotations - 'a and 'b. The returned reference is then tied to
exactly the argument it depends on. That is precise control, rather than an intersection.