Skip to content

← Ownership III: Lifetimes, Explicitly step 8 of 22

Medium Primitives

`longest`: writing your first `'a`

This is the one where you write a lifetime annotation that actually does work.

pub fn longest(a: &str, b: &str) -> &str          // needs an annotation
pub fn longest_of(items: &[String]) -> Option<&str>  // does not

longest returns whichever argument is longer by len(), i.e. by bytes; on a tie it returns a. longest_of returns the longest element of the slice, first-wins on ties, and None for an empty slice.

Why longest cannot elide

Two parameters carry lifetimes, so elision rule 2 does not fire and you get E0106: missing lifetime specifier. The compiler is asking a question: which input does the output come from? Here the honest answer is “either one, I decide at runtime”, and the way to say that is:

pub fn longest<'a>(a: &'a str, b: &'a str) -> &'a str

Re-read what that means, because the intuitive reading is wrong. It does not say “a and b live equally long”. It says: there exists some region 'a over which both inputs are valid, and the result is valid over that same region. At each call site the compiler picks the largest region satisfying that — effectively the overlap of the two inputs’ validity. Lifetimes are constraints, not durations; nothing about this annotation changes when anything is dropped.

The generated test driver leans on exactly that. It builds a in the outer scope, builds b inside a nested block, and calls longest(&a, &b) in there. 'a is inferred as the inner region, the result is usable inside the block, and would be rejected outside it. That is the constraint doing its job, not a limitation.

The two errors you will meet on the way

The starter ships the annotation almost everybody writes first:

pub fn longest<'a, 'b>(a: &'a str, b: &'b str) -> &'a str

Two independent regions, and the return promises 'a. Returning b then fails with a bare, un---explain-able diagnostic:

error: lifetime may not live long enough
   ... function was supposed to return data with lifetime `'a`
       but it is returning data with lifetime `'b`
   = help: consider adding the following bound: `'b: 'a`

No error code. Get used to it — the harder lifetime failures often have none, and hunting for rustc --explain on this one is a dead end. (The suggested 'b: 'a bound would compile, and it is a legitimate signature. It is also strictly more machinery than <'a> on both, so it is not what you want here.)

Now half-fix it and leave b elided:

pub fn longest<'a>(a: &'a str, b: &str) -> &'a str

Returning b from that gives E0621: explicit lifetime required in the type of b: lifetime 'a required. Same underlying complaint, different shape, and this one does have a code. Two spellings of the same mistake, two completely different diagnostics — which is precisely why “read the error” is a skill rather than a slogan.

Why longest_of must not be annotated

longest_of(items: &[String]) has exactly one lifetime-carrying parameter, so elision hands its lifetime to the &str inside the Option for free. Writing longest_of<'a>(items: &'a [String]) -> Option<&'a str> compiles and is correct — and still fails this problem, because clippy::needless_lifetimes is warn-by-default and the gate is -D warnings. One function in this file needs 'a and the other must not have it. That contrast is the lesson.

The tie trap

items.iter().max_by_key(|s| s.len()) returns the last maximum, not the first. Its documentation says so, and it is a real bug generator. The tests include ties. Reach for reduce with a strict > comparison, or fold, or a loop — anything whose tie-breaking you chose on purpose.

The pins

const _: for<'a> fn(&'a str, &'a str) -> &'a str = longest;
const _: for<'a> fn(&'a [String]) -> Option<&'a str> = longest_of;

These coerce your functions into function-pointer types with the lifetimes spelled out. Return a String from either, or over-tie the lifetimes, and the coercion stops compiling. Do not edit or delete them.

Loading visualization…