Skip to content

← Ownership III: Lifetimes, Explicitly step 22 of 22

Medium Primitives

Generics, traits and lifetimes in one signature

Implement three functions that combine generic types with borrowed results. Keep the supplied signature checks and type alias unchanged.

pub fn best_by<T, K>(items: &[T], key: K) -> Option<&T>
where
    K: Fn(&T) -> i64;

pub fn describe<T: Display>(items: &[String], tag: T) -> (String, Option<&str>);

pub fn store<'a, 'b: 'a, T>(long: &'b T, out: &mut Vec<&'a T>);
  • best_by returns a reference to the element with the largest key. Keep the first element on ties, and return None for an empty slice. Do not require elements to implement Copy, Clone or Ord.
  • describe returns an owned summary formatted as <tag>#<count> and a borrowed string slice of the first item. Use the tag's Display output and the number of strings, not their total length. Empty input produces <tag>#0 and None.
  • store appends long to out without changing existing entries. Add the missing outlives bound to the starter's signature; keep its body.

For example, the identity key on [3, 11, 7] selects 11. Describing ["alpha", "beta"] with tag "run" returns ("run#2", Some("alpha")). An empty first string produces Some(""), not None.

Which reference does the result borrow?

In best_by, the output reference borrows from items. The elision rules assign it the lifetime of that input slice, so no named lifetime is needed. The same rule connects describe's returned &str to its items slice. Its summary is a new owned String.

K and T can themselves contain references. That does not introduce extra input reference lifetimes for this elision rule. In particular, describe must not tie its returned slice to the lifetime of a borrowed tag.

Keep these two signatures elided. Naming their otherwise-elidable lifetimes adds no information and triggers the needless_lifetimes Clippy check here.

Expressing an outlives relationship

In store, out holds references valid for 'a, while long is valid for 'b. The bound 'b: 'a means that 'b outlives 'a: it must last at least as long. Equal lifetimes are allowed too.

This lets the function store the longer-lived shared reference where a shorter-lived one is expected. It does not extend the lifetime of the value or make the output vector borrowable for longer. The mutable borrow of the vector itself is separate from the lifetime of the references it stores.

Keep both named lifetimes for this exercise and add their relationship. A different API could use one shared lifetime for long and the stored references; the supplied two-lifetime signature makes the outlives bound explicit. The compiler checks the body against that declared relationship, rather than assuming that every caller happens to pass a long-lived value.

The parameter &'a T also implies T: 'a, so you do not need to add that type-outlives bound. Lifetime parameters come before type parameters in the declaration: <'a, 'b: 'a, T>.

Selecting the first maximum

The supplied keys return consistent results for the same element. The exercise does not prescribe how many times you call a key. When comparing a candidate with the current best, replace the best only for a strictly larger key. Iterator::max_by_key keeps the last maximum, so using it directly would violate the first-on-ties requirement.

Fn(&T) -> i64 allows the key to accept references with different borrow lifetimes. It does not mean the closure must own all its data or be 'static; a closure can also borrow values from its calling scope.

What the harness checks

KeyFn and the two constant function-pointer assignments check particular instances of best_by and describe, including the input/output borrowing relationship. These checks supplement the generic implementations; they do not prove every part of the contract.

For the JSON report, the harness uses best_by twice: once with the value itself as the key, then with -v.rem_euclid(10). The second selects the smallest nonnegative remainder modulo 10, with the first value winning ties. For negative numbers this is not the last digit of the absolute value: -13 has remainder 7.

The report also includes the summary, first word, and values appended through store. Empty selections become JSON null. Inputs may include any i64 value and arbitrary strings; preserve string contents exactly.