We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 21 of 24
Extension traits: adding methods to types you do not own
Rust's orphan rule limits which trait implementations you may write. For
impl Trait for Type, at least one of Trait or Type must be defined in
your own crate (it must be local). You cannot implement a standard-library
trait for a standard-library type:
impl std::fmt::Display for [f64] { /* ... */ } // error[E0117]: both are foreign
One way around this is a newtype: wrap the foreign type in a local struct, which makes the type local. An extension trait goes the other way. You define the trait yourself, so the trait is local, and then implement it for a type you do not own. No wrapper is needed:
pub trait StatsExt { // local trait
fn mean(&self) -> f64;
fn median(&self) -> f64;
}
impl StatsExt for [f64] { // foreign type: allowed, because the trait is local
// ...
}
After this, any f64 slice has .mean() and .median() methods wherever
StatsExt is in scope. Libraries use the same idea. For example,
itertools::Itertools and futures::StreamExt are traits implemented for
every type that implements Iterator or Stream respectively, and you
enable their methods with use itertools::Itertools; or
use futures::StreamExt;.
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)
The trait and summarise are written for you. summarise returns
(mean, median). You write the impl block:
mean: add the values from first to last, then divide by the number of values. An empty slice returns0.0.median: sort a copy of the values in ascending order. For an odd number of values, return the middle one. For an even number, return the mean of the two middle ones, computed as(a + b) / 2.0. An empty slice returns0.0. The input is not necessarily sorted, and must not be modified (the method only has&self).
Every input value is a finite number (the JSON test input cannot express
NaN or infinity), and the test values are small enough that sums do not
overflow. Example: for [10.0, 1.0, 7.0, 3.0, 2.0] the mean is
23.0 / 5.0 = 4.6 and the sorted copy is [1, 2, 3, 7, 10], so the median
is 3.0. For [1.0, 2.0, 3.0, 4.0] the median is (2.0 + 3.0) / 2.0 = 2.5.
Implement it for [f64], not Vec<f64>
summarise calls xs.as_slice() first, so the receiver is a &[f64]. The
trait's methods take &self, so with impl StatsExt for [f64] the self in
each method has type &[f64], which matches. If you instead write
impl StatsExt for Vec<f64>, the call in summarise does not compile:
error[E0599]: no method named `mean` found for reference `&[f64]` in the current scope
The slice implementation also reaches more call sites. Method lookup can
dereference a Vec<f64> to [f64] and convert an array such as [f64; 8]
to a slice, so v.mean(), array.mean() and v[2..5].mean() all work. An
implementation for Vec<f64> only covers receivers that are or dereference
to a Vec<f64> (such as &Vec<f64> or Box<Vec<f64>>); arrays and slices
would not get the methods.
Sorting floats
median needs sorted data, but slice::sort requires T: Ord, and f64
does not implement Ord, because NaN is not equal to itself and is
neither less than nor greater than any number. Calling .sort() on a
Vec<f64> fails with E0277 (the trait bound f64: Ord is not satisfied).
Two common replacements:
sorted.sort_by(f64::total_cmp); // total order, no panic
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); // unwrap panics if a NaN is compared
f64::total_cmp defines an order for every f64 value, including NaN:
negative NaN sorts before negative infinity, positive NaN sorts after
positive infinity, and -0.0 sorts before 0.0. It never panics and needs
no unwrap, so it is the safer choice even though this exercise's inputs
contain no NaN.
A separate point about custom comparators: the sort_by documentation says
that if the comparator is not a total order, the sort may panic, and if it
returns normally the resulting order is unspecified. Do not rely on either
outcome.
The trait must be in scope
An extension trait's methods can only be called where the trait is in scope.
If StatsExt lived in a module stats and the caller did not import it,
the call would fail like this:
error[E0599]: no method named `mean` found for reference `&[f64]` in the current scope
= help: 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
|
1 + use crate::stats::StatsExt;
The suggested use line is the fix. In this exercise the trait, the
implementation and summarise are all in one file and one module, so no
import is needed here.
Imports can also cause the opposite problem. If two traits in scope both
provide a mean method for [f64], for example because two glob imports
(use a::*; use b::*;) each bring one in, then x.mean() is ambiguous and
fails with E0034 (multiple applicable items in scope). You can resolve it
by calling one explicitly, such as a::A::mean(x), or by importing only the
trait you need. Clippy's wildcard_imports lint warns about glob imports,
but it belongs to the opt-in clippy::pedantic group, so -D warnings does
not enable it.
Your submission must compile, pass the tests and pass
cargo clippy -- -D warnings.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.