Home > Blockchain >  How can I satisfy the `Sum<T>` trait requirement for f32?
How can I satisfy the `Sum<T>` trait requirement for f32?

Time:12-17

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 over T.

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`

Playground

2. Result of summing is T

When you sum up a bunch of i32s you're going to get an i32. When you sum a bunch of f64s 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

Playground

4. T must be divisible by f32

We're close, right? Let's require that T be divisible by f32s:

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`

Playground

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.

Playground

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
    }
}

Playground

  • Related