 # Generic function over integer slice

This seems to be a common question, but I've yet to find a good solution. In general I'd like to find an approach for writing a generic function that works for all signed and unsigned integer types. For example, I'd like to write a function that returns the average of a slice of such numbers. Here is my failed attempt.

``````extern crate num;
use std::iter::Sum;

fn average<T>(numbers: &[T]) -> f32
where
T: num::PrimInt + Sum
{
let sum: T = numbers.iter().sum() as f32;
sum / numbers.len() as f32
}

fn main() {
let scores = vec![70, 90, 100];
println!("average = {:.1}", average(&scores)); // should output 86.7
}``````
1 Like

I got this to work with some help from Why is i32::from(f) not allowed but 'f as i32' is? - #3 by jdahlstrom .

``````extern crate num;
use std::iter::Sum;

pub trait ToF32 {
fn to_f32(v: Self) -> f32;
}

impl ToF32 for i32 {
fn to_f32(v: Self) -> f32 {
return v as f32;
}
}

fn average<'a, T>(numbers: &'a[T]) -> f32
where
T: num::PrimInt + Sum<&'a T> + ToF32,
{
let sum : T = numbers.iter().sum();
ToF32::to_f32(sum) / numbers.len() as f32
}

fn main() {
let scores = vec![70, 90, 100];
println!("average = {:.1}", average(&scores)); // should output 86.7
}
``````

1 Like

You can do this:

``````fn average<T>(numbers: &[T]) -> f32
where
T: num::PrimInt + Clone + Sum,
{
let sum: f32 = numbers.iter().cloned().sum::<T>().to_f32().unwrap();
sum / numbers.len() as f32
}
``````

The `to_f32` method is available due to the `PrimInt` bound.

3 Likes

Whoops, I did not know that `PrimInt` brings `to_f32`. With that the custom Trait is not needed in my solution and it becomes

``````fn average<'a, T>(numbers: &'a[T]) -> f32
where
T: num::PrimInt + Sum<&'a T>,
{
let sum : T = numbers.iter().sum();
sum.to_f32().unwrap() / numbers.len() as f32
}
``````
1 Like

The compiler errors provide a reasonable guide for fixing this code. If we ignore everything except the first error each time, the process looks like this:

``````error[E0308]: mismatched types
--> src/main.rs:8:18
|
4 | fn average<T>(numbers: &[T]) -> f32
|            - this type parameter
...
8 |     let sum: T = numbers.iter().sum() as f32;
|              -   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found `f32`
|              |
|              expected due to this
|
= note: expected type parameter `T`
found type `f32`
``````

You're trying to store an `f32` into a variable of type `T`. Make the variable `f32` instead:

``````let sum: f32 = numbers.iter().sum() as f32;
``````

``````error[E0282]: type annotations needed
--> src/main.rs:8:35
|
8 |     let sum: f32 = numbers.iter().sum() as f32;
|                                   ^^^ cannot infer type for type parameter `S` declared on the associated function `sum`
|
= note: type must be known at this point
help: consider specifying the type argument in the method call
|
8 |     let sum: f32 = numbers.iter().sum::<S>() as f32;
|                                      ^^^^^
``````

Ok; we're summing `T`'s, so let's specify that the way the compiler hint suggests:

``````let sum: f32 = numbers.iter().sum::<T>() as f32;
``````

``````error[E0277]: the trait bound `T: Sum<&T>` is not satisfied
--> src/main.rs:8:35
|
8 |     let sum: f32 = numbers.iter().sum::<T>() as f32;
|                                   ^^^ the trait `Sum<&T>` is not implemented for `T`
|
help: consider further restricting this bound
|
6 |     T: num::PrimInt + Sum + Sum<&T>,
|                           ^^^^^^^^^
``````

The iterator is providing `&T`s instead of `T`s. Primitive integers are `Copy`, so we can use the `copied` method to get past the reference:

``````let sum: f32 = numbers.iter().copied().sum::<T>() as f32;
``````

``````error[E0605]: non-primitive cast: `T` as `f32`
--> src/main.rs:8:20
|
8 |     let sum: f32 = numbers.iter().copied().sum::<T>() as f32;
|                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
``````

We can't use an `as` cast to convert between the generic type `T` and the concrete one `f32`, so we need another way to do this conversion. Fortunately, there's `PrimInt::to_f32`:

``````let sum: f32 = numbers.iter().copied().sum::<T>().to_f32();
``````

``````error[E0308]: mismatched types
--> src/main.rs:8:20
|
8 |     let sum: f32 = numbers.iter().copied().sum::<T>().to_f32();
|              ---   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `f32`, found enum `Option`
|              |
|              expected due to this
|
= note: expected type `f32`
found enum `Option<f32>`
``````

It apparently returns an `Option`. In the absence of anything sensible to do with the conversion failure, we can just `unwrap` it:

``````let sum: f32 = numbers.iter().copied().sum::<T>().to_f32().unwrap();
``````

``````average = 86.7
``````

Looks like we're done at this point. The process isn't particularly hard, but you do sometimes need to go several rounds with the compiler before reaching code that works.

12 Likes

I see that this works, but would a Rustacean view this as a good solution? Would you use this approach or would you prefer to just implement different functions for each integer type you need to support? A line like `let sum: f32 = numbers.iter().cloned().sum::<T>().to_f32().unwrap();` seems pretty frightening compared to what people are used to in other languages.

I wonder if there is also a solution that just requires the type in the slice to implement traits like `std::ops::Add` and `std::ops::Div`.

Personally I would only define the method with generics if I actually need to call it with three or more different types of integers. With one or two, just hard-code it. A less scary method might be to convert them to floats before summing:

``````fn average<T>(numbers: &[T]) -> f32
where
T: num::PrimInt,
{
let sum: f32 = numbers.iter().map(|n| n.to_f32().unwrap()).sum();
sum / numbers.len() as f32
}
``````

or to use a loop:

``````fn average<T>(numbers: &[T]) -> f32
where
T: num::PrimInt,
{
let mut sum = 0.0;

for num in numbers {
sum += num.to_f32().unwrap();
}

sum / numbers.len() as f32
}
``````
4 Likes

That is much less scary! Thanks!

How could I have discovered that the `PrimInt` trait supports the `to_f32` method?
I don't see it at num::traits::PrimInt - Rust.

BTW: After reading alice's solution to a different topic, I saw that my proposal does not require an explicit lifetime in the signature, either:

``````extern crate num;
use std::iter::Sum;

fn average<T>(numbers: &[T]) -> f32
where
T: num::PrimInt,
T: for<'a> Sum<&'a T>
{
let sum : T = numbers.iter().sum();
sum.to_f32().unwrap() / numbers.len() as f32
}

fn main() {
let scores = vec![70, 90, 100];
println!("average = {:.1}", average(&scores)); // outputs 86.7 as expected
}
``````
2 Likes

It’s in `ToPrimitive` which is one of the supertraits of `PrimInt`.

1 Like

I admit that it is not easy to find the `to_f32` method on the docs. You have to click "show declaration" at the top, which reveals its super-traits. Then you find that it has `NumCast` as a super-trait, but that trait doesn't directly expose a suitable method (`NumCast` is for the opposite conversion). But then by clicking "show declaration" again, you see that `ToPrimitive` is a super-trait of `NumCast`, and the method is on `ToPrimitive`.

3 Likes

Taking this one step farther, I wonder if I can use the `num` crate to write an `average` function that also works for float values by using the `Num` trait instead of the `PrimInt` trait. Here is my start on it:

``````extern crate num;

fn average<T: num::Num>(numbers: &[T]) -> f32 {
let mut sum = T::zero();
for n in numbers {
sum = sum + *n;
}
(sum / numbers.len()).to_f32() // errors here
}

fn main() {
let scores = vec![70, 90, 85, 100];

// Print average of all scores.
println!("average = {:.1}", average(&scores)); // 86.2

// Print average of all scores except the first.
println!("average = {:.1}", average(&scores[1..])); // 91.7
}
``````

Well it fails because `numbers.len()` might not return the type `T` as it always returns `usize`. You need to make the types match!

1 Like

Yeah, but I don't know how to convert a `usize` to `num::Num` which implements the `Div` trait. If I could do that, the my next problem would be finding a way to call the `to_f32` method on a `Num`. It doesn't look like there's a way to do that, so I don't know how to provide the result type of `f32`.

Alternatively I could try convert `sum` to an `f32` so I could do the division on built-in types, but I don't know how to do that either.

If you specify that `T` satisfies `ToPrimitive` instead of (or in addition to) `Num`, you should be good because then you can be sure that you can call `to_f32` on an instance of `T`.

2 Likes

Making progress! Here's a working implementation.
I think I have just one more question on this.
`Num` implements the `Add` trait, so the line `sum = sum + *n;` works.
But `Num` does not implement the `AddAssign` trait, so it seems I cannot use `sum += *n;`.
However, if I say that `T` must implement `AddAssign` then I can use that.
How is it that I can just say I want that and `Num` magically gains that ability?

``````extern crate num;
use num::{Num, ToPrimitive};

fn average<T: AddAssign + Copy + Num + ToPrimitive>(numbers: &[T]) -> f32 {
let mut sum = T::zero();
for n in numbers {
//sum = sum + *n; // only requires Add which Num implements
sum += *n; // requires AddAssign
}
let numerator = sum.to_f32().unwrap();
numerator / numbers.len() as f32
}

fn main() {
//let scores = vec![70, 90, 85, 100];
let scores = vec![70.1, 90.2, 85.3, 99.4];
println!("average = {:.1}", average(&scores)); // 86.2
}
``````

Is specifying the trait bounds in the angle brackets discouraged when there is more than one?
Is using a `where` clause considered more idiomatic?

`Num` doesn't gain the ability; you've told the compiler to reject any attempts to call `average` unless it can prove the given `T` implements all of the traits you've listed. If you had some other function like this:

``````fn f<T:Num + ToPrimitive + Copy>(...
``````

then it would be unable to call `average`: its `T` doesn't necessarily implement `AddAssign`, so the compiler won't allow it.

4 Likes

Thanks so much for explaining that! It's a piece of the puzzle that I hadn't yet grasped.

For things that need floating-point to be reasonable, I often only care about the two primitives, so a middle ground would be to define those two functions with a macro. Perhaps something like this:

``````macro_rules! define_average {
(\$name:ident \$t:ty) => {
fn \$name(iter: impl ExactSizeIterator<Item = \$t>) -> Option<\$t> {
let len = iter.len();
if len == 0 { None }
else { Some(iter.sum::<\$t>() / (len as \$t)) }
}
}
}

define_average!(average_f32 f32);
define_average!(average_f64 f64);

fn main() {
assert_eq!(average_f32(vec![1.0, 2.0, 3.0].into_iter()), Some(2.0));
assert_eq!(average_f64(vec![1.0, 2.0, 3.0].into_iter()), Some(2.0));
}
``````
1 Like