We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership III: Lifetimes, Explicitly step 9 of 22
Two inputs, one output lifetime
The previous problem taught you to tie two inputs together with one 'a. This
one teaches the opposite, and it is the more common case by a wide margin.
pub fn first_match<'a>(haystack: &'a [String], pattern: &str) -> Option<&'a str>
pub fn longest_common_prefix<'a>(a: &'a str, b: &str) -> &'a str
first_match returns the first element of haystack that contains
pattern as a substring, or None. An empty pattern is contained in
everything, so it matches the first element. longest_common_prefix returns
the longest common prefix of a and b as a slice of a.
The design mistake this problem exists to prevent
Both functions get E0106 if you elide, because both have two lifetime-carrying
parameters. The reflex fix — tie everything to one 'a — compiles:
pub fn first_match<'a>(haystack: &'a [String], pattern: &'a str) -> Option<&'a str>
It passes every runtime test. It is also wrong, and it is the number-one lifetime design smell in real crates. Ask what it promises: the returned reference is valid only over a region where both the haystack and the pattern are valid. But the returned string lives inside the haystack. The pattern was never involved. You have made every caller keep their search term alive for as long as they want to hold the result — for no reason at all.
Look at what the generated driver does:
let found: Option<&str>;
{
let pattern = input.get("pattern").as_str().to_string();
found = first_match(&haystack, &pattern);
}
// pattern is gone. `found` is still perfectly good.
That is a completely ordinary thing for a caller to write — build a scratch
search term, use it, drop it, keep the hit. The over-tied signature makes it
E0597: pattern does not live long enough, and the caller has no way to
fix it short of restructuring their own code around your API’s imagination.
The correct rule is mechanical: give 'a only to the parameters the return
value is actually made of. Everything else gets its own elided lifetime,
which you never have to name.
The pins are the only enforcement here
const _: for<'a, 'b> fn(&'a [String], &'b str) -> Option<&'a str> = first_match;
const _: for<'a, 'b> fn(&'a str, &'b str) -> &'a str = longest_common_prefix;
Two binders, deliberately. A for<'a, 'b> function-pointer type demands a
function that works for any pair of unrelated regions, so an over-tied
submission fails to coerce with E0308: mismatched types right at the pin.
A single-binder pin (for<'a> fn(&'a [String], &'a str) -> ...) would happily
accept the wrong version — that is the trap, and it is why the pins look the
way they do.
Note also what clippy::redundant_clone would not have caught: nothing here
stops you cloning the haystack and returning an owned String… except that
the pin says Option<&'a str>. Runtime assertions cannot see lifetimes. The
pins are the grader. Do not edit or delete them.
The multibyte trap in longest_common_prefix
The obvious implementation walks bytes:
let n = a.bytes().zip(b.bytes()).take_while(|(x, y)| x == y).count();
&a[..n] // <- panics
Feed it a = "héllo", b = "hèllo". é is 0xC3 0xA9, è is 0xC3 0xA8.
The first bytes of both are h and 0xC3, so n == 2 — which lands inside
é. Slicing a str at a byte offset that is not a character boundary is a
runtime panic, not a compile error, and one of the tests does exactly this.
Iterate over chars (char_indices gives you the byte offset of each), and
every index you produce is a boundary by construction.
Watch the empty-tail case too: if one string is a prefix of the other there is no mismatching character at all, and the answer is the shorter of the two — measured in bytes, since that is what you index with.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.