Skip to content

← Generics and Traits step 21 of 24

Medium Primitives

Extension traits: adding methods to types you do not own

The previous problem made the orphan rule feel like a wall. This one makes it a tool.

The rule says: for impl Trait for Type, at least one of the two must be local. The newtype escape makes the type local. The extension trait pattern makes the trait local instead — and then you can implement it for any foreign type you like, with no wrapper at all:

pub trait StatsExt {        // local trait
    fn mean(&self) -> f64;
    fn median(&self) -> f64;
}

impl StatsExt for [f64] { } // foreign type — perfectly legal

Say the halves out loud before you write it: the trait is mine, the type is std’s. That is the allowed direction. The reverse — impl std::fmt::Display for [f64] — is E0117.

This is the pattern behind every use something::SomethingExt; line you have seen in real Rust code. itertools::Itertools, rand::Rng, futures::StreamExt are all local traits blanket- or specifically-implemented for foreign types, and the use is what switches them on.

Your task

pub trait StatsExt { fn mean(&self) -> f64; fn median(&self) -> f64; }
impl StatsExt for [f64] { ... }

pub fn summarise(xs: Vec<f64>) -> (f64, f64)
  • mean — the arithmetic mean. Empty slice returns 0.0.
  • median — the middle value of the sorted data for odd lengths, the mean of the two middle values for even lengths. Empty slice returns 0.0.

summarise returns (mean, median) and is written for you.

Implement it for [f64], not Vec<f64>

This is the detail that matters, and the starter forces it: summarise calls xs.as_slice() first, so the receiver really is a &[f64]. If you write impl StatsExt for Vec<f64> you get

error[E0599]: no method named `mean` found for reference `&[f64]`

Implementing on the slice is also strictly better. [f64] is the borrowed form, so your methods become available on Vec<f64> (via deref), on [f64; 8] (via unsizing), and on any sub-range like &xs[2..5]. Implementing on Vec<f64> gets you exactly one of those three.

Sorting floats, correctly

median needs the data sorted, and slice::sort requires T: Ord, which f64 is not — you saw why earlier in this track. Two idiomatic options:

sorted.sort_by(f64::total_cmp);                          // total order, no panic
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());        // panics on NaN

Prefer total_cmp. It is a genuine total order (it files NaN at the ends and distinguishes -0.0 from 0.0), it never panics, and it needs no unwrap.

A related warning about comparators in general: if you hand a sort an inconsistent comparator, the documentation says it may panic — and may equally return normally with the elements in an unspecified order. Never write code that depends on the panic happening.

The invisible half of the pattern

In real multi-module code, an extension trait’s methods are only callable where the trait is in scope. Forget the use and you get:

error[E0599]: no method named `mean` found for reference `&[f64]`
              items from traits can only be used if the trait is in scope
help: trait `StatsExt` which provides `mean` is implemented but not in scope;
      perhaps you want to import it

That is the classic symptom, and the help: line is the fix. In this single-file harness everything is already in one scope, so the requirement is real but invisible here. Remember it exists; it is the reason a crate that offers extension traits documents the import on the first line of its README.

It is also why clippy::wildcard_imports matters. use foo::*; pulls in every extension trait a module happens to define, and each one joins method resolution for every type it covers — which is exactly how you end up with a surprise E0034.

Remember the grade is compile + tests + clippy -D warnings.