I have the following trait that I am trying to implement:
pub trait CentralMoment<Output = f32>
where
Output: Copy,
{
fn mean(&self) -> Output;
}
impl<T> CentralMoment for [T] {
fn mean(&self) -> f32 {
let sum: f32 = self.iter().sum();
sum / self.len() as f32
}
}
My issue is with the line let sum: f32 = self.iter().sum()
. The compiler is telling me:
the trait bound `f32: Sum<&T>` is not satisfied
--> src/lib.rs:45:36
|
45 | let sum: f32 = self.iter().sum();
| ^^^ the trait `Sum<&T>` is not implemented for `f32`
|
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
|
42 | T: Copy, f32: Sum<&T>
but even when I try include f32: Sum<&T>
I still get the same error.
What am I doing wrong here and how can I resolve this issue? Thanks in advance, and please let me know if you need any further clarification.
CodePudding user response:
Arithmetic with generics is tricky. Getting all of the constraints and types right is like a game of whack-a-mole. Every time you fix an error another is going to pop up. I don't want to jump straight to the answer; it's better if we go through each of the steps one-by-one. That said, if you just want to see the solution it's at the bottom.
Let's start whacking.
1. Copy the numbers
Take a look at the implementors for Sum
. They look like this:
impl Sum<f32> for f32
impl Sum<f64> for f64
impl Sum<i8> for i8
impl Sum<i16> for i16
impl Sum<i32> for i32
...
And so on.
The general pattern is obvious:
impl Sum<T> for T
None of them match your code because it's apparently trying to use Sum<&T>
. We need to get rid of the references so Sum<&T>
can be Sum<T>
.
Where are the references coming from? iter()
iterates over item references. It does this to avoid modifying the collection or its items.
We could use into_iter()
to iterate over the items directly. Let's not, though. into_iter()
converts a collection into an iterator. Converts... as in destroys. It moves all the items and consumes the collection in the process. Yikes!
Instead, let's copy the items with copied()
:
This is useful when you have an iterator over
&T
, but you need an iterator overT
.
let sum: f32 = self.iter().copied().sum();
We'll also need a T: Copy
constraint:
impl<T> CentralMoment for [T]
where
T: Copy,
This changes the error to:
error[E0277]: the trait bound `f32: Sum<T>` is not satisfied
--> src/lib.rs:13:45
|
13 | let sum: f32 = self.iter().copied().sum();
| ^^^ the trait `Sum<T>` is not implemented for `f32`
2. Result of summing is T
When you sum up a bunch of i32
s you're going to get an i32
. When you sum a bunch of f64
s you're going to get an f64
. This code says the result of sum()
will be an f32
, but really to be properly generic you should call it a T
.
let sum: T = self.iter().copied().sum();
Now the error is:
error[E0277]: the trait bound `T: Sum` is not satisfied
--> src/lib.rs:13:43
|
13 | let sum: T = self.iter().copied().sum();
| ^^^ the trait `Sum` is not implemented for `T`
3. T
must be summable
We can fix that requiring that T: Sum
:
where
T: Copy Sum,
Now we get:
error[E0369]: cannot divide `T` by `f32`
--> src/lib.rs:16:13
|
16 | sum / self.len() as f32
| --- ^ ----------------- f32
| |
| T
4. T
must be divisible by f32
We're close, right? Let's require that T
be divisible by f32
s:
where
T: Copy Sum Div<f32>,
Are you happy, compiler?
error[E0308]: mismatched types
--> src/lib.rs:17:9
|
15 | fn mean(&self) -> f32 {
| --- expected `f32` because of return type
16 | let sum: T = self.iter().copied().sum();
17 | sum / self.len() as f32
| ^^^^^^^^^^^^^^^^^^^^^^^ expected `f32`, found associated type
|
= note: expected type `f32`
found associated type `<T as Div<f32>>::Output`
= help: consider constraining the associated type `<T as Div<f32>>::Output` to `f32`
No, of course not. Why am I not surprised?
5. Division must return an f32
What now?
Well, we've required that T
be divisible by f32
. We haven't told it what type the result would be, though. It's not a given that operand types and result types are the same. They could be different. Rust is oh-so-flexible.
We need to put a constraint on the ::Output
of the operation. Let's require that the output also be an f32
:
where
T: Copy Sum Div<f32, Output = f32>,
Result:
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized debuginfo] target(s) in 0.91s
Hallelujah! The princess is not in another castle.
Solution
use std::iter::Sum;
use std::ops::Div;
pub trait CentralMoment<Output = f32>
where
Output: Copy,
{
fn mean(&self) -> Output;
}
impl<T> CentralMoment for [T]
where
T: Copy Sum Div<f32, Output = f32>,
{
fn mean(&self) -> f32 {
let sum: T = self.iter().copied().sum();
sum / self.len() as f32
}
}