Skip to content

← Generics and Traits step 3 of 24

Easy Primitives

Trait bounds and why f64 is not Ord

Correct the trait bound on max_of so the supplied implementation works for both integers and floating-point values. Keep its reduction and the extremes wrapper unchanged.

pub fn max_of<T: PartialOrd + Copy>(items: &[T]) -> Option<T>
pub fn extremes(ints: Vec<i64>, floats: Vec<f64>) -> (Option<i64>, Option<f64>)

extremes calls the same generic function for each input vector. For this exercise's integers and non-NaN floats, return the largest value from each vector, or None when that vector is empty.

Integers Floats Result
[3, 9, 2] [1.5, 0.25, 7.75] (Some(9), Some(7.75))
[] [-2.5] (None, Some(-2.5))
[-5, -1, -9] [] (Some(-1), None)

Each vector is independent. Do not substitute zero for an empty vector or use it as the initial maximum: that would also give the wrong answer for inputs containing only negative values.

Ask for the capabilities the body uses

The body borrows the slice, copies each element, then reduces the values:

items.iter().copied().reduce(|best, x| if x > best { x } else { best })

PartialOrd permits the > comparison. Copy lets .copied() turn each &T into an owned T without moving it out of the borrowed slice. The trait bounds describe what the generic body may rely on; callers must satisfy them.

reduce uses the first element as its initial candidate and returns None for an empty iterator. Each later element replaces the candidate only when it is strictly greater. Equal values keep the earlier candidate, so the function preserves the first maximum.

Why the starter rejects f64

The starter requires T: Ord + Copy. That works for i64, but the call with floats fails because f64 does not implement Ord. Read the diagnostic's unmet bound and the note pointing to the function signature.

Ord requires a total ordering consistent with equality. Ordinary floating comparisons include unordered cases: NaN is neither less than, greater than, nor equal to 1.0, and is not even equal to itself. f64 implements PartialOrd, whose partial_cmp can return None for incomparable values.

This does not mean floats cannot be given a total order. f64::total_cmp provides an explicit ordering policy, including NaNs and signed zero. That policy differs from ordinary float equality and comparison, so using it is a separate choice rather than an Ord implementation on f64.

The limit of this reduction

The exercise inputs exclude NaN. More generally, a PartialOrd bound does not promise that every pair is comparable. In this reduction, an incomparable comparison makes x > best false, so the current candidate is retained. For example, an initial NaN would remain the result, while a later NaN would be skipped when the current candidate is a number.

Treat this as a defined selection rule, not a guarantee of a greatest element for every possible partially ordered type. Choosing how to handle unordered values belongs in the contract of any broader API you build.

Keep both bounds

Removing Copy breaks the existing .copied() call. Removing the ordering bound makes the generic > comparison invalid. The fix here is to relax Ord to PartialOrd while retaining Copy, not to add conversions, clone the input vector or duplicate the integer and float implementations.

The harness represents None as JSON null. The solution must compile, return the expected values and pass clippy -D warnings.