Step 2 of 7
Lifetime annotations on functions
Inside one function, the borrow checker sees everything and needs no help. Between functions it's different: when it checks a call, it looks only at the called function's signature, never its body. So when a function returns a reference, the signature has to say which input the result borrows from.
fn longest(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longest("ferris", "crab"));
}
error[E0106]: missing lifetime specifier
--> src/main.rs:1:33
|
1 | fn longest(a: &str, b: &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 `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
The help line already has the answer. A lifetime annotation is a name that starts with an apostrophe, usually 'a. You declare it in <> after the function name, like a generic type parameter, and write it after the &:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let title = String::from("Rustacean Station");
{
let other = String::from("Crab Talk");
let result = longest(&title, &other);
println!("{result}");
}
}
Rustacean Station
Read fn longest<'a>(a: &'a str, b: &'a str) -> &'a str as: "there's some stretch of code 'a during which both a and b are valid; the result is valid during that stretch too". At a call, the compiler picks 'a as the overlap of the two arguments' lifetimes. So the result may be used only while both inputs are alive.
That's why this doesn't compile, even though the result would actually be title:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let title = String::from("Rustacean Station");
let result;
{
let other = String::from("Crab Talk");
result = longest(&title, &other);
}
println!("{result}");
}
error[E0597]: `other` does not live long enough
The compiler goes by the signature, not by which branch runs: the result might be other, and other is dropped at the }.
Tie the result only to what it really borrows from
If the result comes from just one parameter, put 'a only on that one. The other parameter gets its own, separate lifetime (you can leave it unwritten):
fn after_prefix<'a>(line: &'a str, prefix: &str) -> &'a str {
if line.starts_with(prefix) {
&line[prefix.len()..]
} else {
line
}
}
fn main() {
let log = String::from("ERROR: disk full");
let msg;
{
let prefix = String::from("ERROR: ");
msg = after_prefix(&log, &prefix);
}
println!("{msg}");
}
disk full
Here prefix may be dropped early, because the signature promises the result borrows from line only. If the body then tried to return prefix, the compiler would refuse with error[E0621]: explicit lifetime required in the type of `prefix` : the body must keep the signature's promise.
Common mistakes
- Thinking
'amakes something live longer. Annotations only describe relationships so the compiler can check them. If the data really is dropped too early, no annotation fixes it; move the data or own it. - Tying every parameter to
'aout of habit. It works inside the function but makes callers' lives harder (likelongestabove). Tie the output only to the inputs it can come from. - Annotating a function that doesn't return a reference.
fn count(a: &str, b: &str) -> usizeneeds no lifetimes at all; there's nothing to describe.
Your task
Two players finish a game. Make winner compile by adding lifetime annotations, and write its body: it returns the name with the higher score (on a tie, name_a). The result can come from either name.
Challenge 2
Write pick with lifetime annotations: it returns the item at index as a &str, or fallback if the index is out of range. Both items and fallback can end up as the result.
Challenge 3
Write trim_comment: it returns the part of line before the first marker, with trailing spaces removed, or the whole line if there's no marker. For example, trim_comment("x = 5 # set x", "#") is "x = 5".
The result only ever borrows from line, so tie it only to line. One hidden test drops the marker string before using the result, which compiles only if the signature says the result doesn't depend on marker.
Previous: Why lifetimes exist: scopes and dangling references Next: The elision rules