Skip to content

← Ownership III: Lifetimes, Explicitly step 8 of 22

Medium Primitives

longest: writing your first 'a

Return a borrowed string without copying its contents. This exercise compares two functions: one needs a named lifetime to connect its inputs and output; the other can use Rust's lifetime-elision rules.

Your task

Fix longest and implement longest_of:

Function Required result
longest(a, b) The longer input by byte length; return a on a tie.
longest_of(items) A borrowed view of the first longest string, or None for an empty slice.

Keep the two function-pointer declarations below the functions. Return &str values borrowed from the inputs, not new String values. An empty string is still an element: longest_of(&[String::new()]) returns Some(""), not None.

Length means UTF-8 bytes. For example, "é" and "ab" both have length 2, so longest("ab", "é") returns "ab". Do not compare character counts.

Connect both inputs to the result

In fn longest(a: &str, b: &str) -> &str, the two input references have separate elided lifetimes. The signature does not say which one constrains the returned reference, so Rust reports a missing lifetime specifier.

The function can return either argument. A shared lifetime expresses that:

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

Both inputs must be valid for the lifetime used by the result. Their owners do not need identical scopes, and the annotation does not change when either owner is dropped. A caller can borrow a longer-lived value for a shorter period so both inputs meet this contract.

The caller must respect the signature even when it can predict which branch will win. It cannot keep the result past an input's valid borrow just because that input happens to be shorter.

Read the starter's error

The starter gives the inputs independent lifetimes, then promises a result valid for the first one:

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

Returning b cannot satisfy that promise without a relationship between 'b and 'a. Use the shared-lifetime signature above. Adding 'a only to the first input and output still leaves the second input unrelated.

Let longest_of use elision

pub fn longest_of(items: &[String]) -> Option<&str>

There is only one input reference lifetime, so the elision rules connect it to the reference inside Option. The returned slice borrows from a string owned by items.

An explicit equivalent lifetime is valid Rust, but unnecessary here. Keep this signature elided: the exercise's Clippy gate rejects needless lifetime annotations. This is a style check, not a memory-safety error.

To keep the first longest element, replace your current choice only when a later string is strictly longer. max_by_key chooses the last maximum on a tie, so using it directly would violate the requested behavior. A loop, fold or reduce can keep the first match.

What the driver checks

The driver creates the second input in an inner block. It calls longest there and copies the result into an owned String before leaving the block. That owned copy can survive the second input; the borrowed result could not.

The supplied declarations also require compatible borrowed-return types:

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

for<'a> means the function pointer must support any lifetime chosen for a call. These declarations catch incompatible signatures, such as returning an owned String or requiring static inputs. They do not prove that the function selects the correct string; the value tests check lengths, ties and emptiness.