Having trouble defining a fn for generic iterator with generic numeric items


#1

I’m a novice Rust programmer. What I would like to do is a function such as this that operates over any iterable with numbers. Here is the function I started with as a base:

fn multiplier(values: &Vec<f64>) -> f64 {
    values.iter().fold(1.0, |acc, val| acc * val)
}

I’ve tried quite a few things, and although I feel like I am getting closer I am now feeling somewhat defeated.

Here is a sorry tale of a few attempts and my thought patterns as I was working through the issues:

At first, I thought that it might be a matter of saying that I need an iterator that has items that can be multiplied. I’m pretty much shooting in the dark at this stage, so I plug this in and see what happens:

use std::ops::{Mul}; // it took reading a few error messages to do this 

fn multiplier<I:Iterator, T:Mul>(vals: I<T>) -> T{
     vals.fold(1.0, |acc, val| acc * val)
}

That code seems to make the compiler very unhappy.

I try searching the web for “generic iterators”. I came across SO question that references a reddit thread that seems authoritative and tried modifying the syntax to suit. (As an aside, my naive eyes see the second version in that example as much clearer, despite it apparently being the wrong way to do things.)

fn multiplier<I:Iterator>(vals: I) -> f64 where I::Item: Mul  {
    vals.fold(1.0, |acc, val| acc * val) as f64
}
<anon>:19:31: 19:40 error: the trait `core::ops::Mul<<I as core::iter::Iterator>::Item>` is not implemented for the type `_` [E0277]

I don’t really understand this explanation. Where did the underscore type come from? Still, I press on slightly. Maybe it would help if I declare T to be Mul, the implication being that T would some how magically connected to I.

fn multiplier<I:Iterator, T:Mul>(vals: I) -> f64 where I::Item: Mul  {
     vals.fold(1.0, |acc, val| acc * val)
}
<anon>:19:32: 19:41 error: the trait `core::ops::Mul<<I as core::iter::Iterator>::Item>` is not implemented for the type `f64` [E0277]

Sigh. The f64 type doesn’t know how to multiply. I think that is what this is saying?

Maybe if I remove the specific type on the return value?

fn multiplier<I:Iterator, T>(vals: I) -> T where I::Item: Mul  {
    vals.iter().fold(1.0, |acc, val| acc * val)
} 
<anon>:61:11: 61:15 error: no method named `iter` found for type `I` in the current scope
<anon>:61      vals.iter().fold(1.0, |acc, val| acc * val)

Nope.

Try into_iter()?

fn multiplier<I:Iterator, T>(vals: I) -> T where I::Item: Mul  {
    vals.into_iter().fold(1.0, |acc, val| acc * val)
}
<anon>:65:27: 65:30 error: mismatched types:
 expected `T`,
    found `_`
(expected type parameter,
    found floating-point variable) [E0308]
<anon>:65     vals.into_iter().fold(1.0, |acc, val| acc * val)
                                    ^~~

This kinda sorta looks like it’s a bit happier. But it wants a T. As I think to myself “How do I say that I want 1*T where T is numeric?”, I do as I am asked…

fn multiplier<I:Iterator, T:Mul>(vals: I) -> T where I::Item: Mul  {
    vals.into_iter().fold(T, |acc, val| acc * val) 
}
<anon>:73:27: 73:28 error: unresolved name `T` [E0425]
<anon>:73     vals.into_iter().fold(T, |acc, val| acc * val) 
...
<anon>:73:47: 73:50 error: mismatched types:
 expected `T`,
    found `<I as core::iter::Iterator>::Item`
(expected type parameter,
    found associated type) [E0308]
<anon>:73     vals.into_iter().fold(T, |acc, val| acc * val) 
                                                        ^~~

Even though I was fairly sure that it wouldn’t work, it’s still disappointing to see red pop up on screen.

Any suggestions?


#2

I think this is what you want:

use std::ops::Mul;

fn multiplier<'a, I, T>(init: T, vals: I) -> T 
    where I:Iterator<Item=&'a T>, T: 'static+Mul<Output=T>+Copy
{
     vals.fold(init, |acc, &val| acc * val)
}

fn main() {
    let data   = vec![1.0, 2.0, 3.0, 4.0];
    let result = multiplier(1.0, data.iter());
    
    println!("{}", result);
}

#3

For more a more detailed explaination if you look at the documentation for std::slice::Iter. It implements Iterator with:

type Item = &'a T

So it’s iterating over a reference to T, that’s why I specified I:Iterator<Item=&'a T>.

Then if you look at the documentation for std::ops::Mul it has a type Output so I needed to add the T: Mul<Output=T> so the compiler knew the output type of the multiplication. The 'static was needed to ensure T lived long enough and the Copy was needed so we could deference the value.


#4

FWIW, you only need T: 'a, not 'static.

You can make this even more generic:

fn multiplier<I, T>(init: T, vals: I) -> T 
    where I: Iterator, T: Mul<I::Item, Output=T>
{
    vals.fold(init, |acc, val| acc * val)
    // or even just vals.fold(init, Mul::mul)
}

This will accept any iterator as long T knows how to multiply its Item. This works for your f64 example because the standard library has impl<'a> Mul<&'a f64> for f64 (with Output=f64).


#5

Can you write mostly same function, but instead of init value use I::Item::default (from Default trait)?

I tried and failed


#6

Your I::Item is a reference, which has no default. If you constrain T: Default, then you can call T::default().


#7

Default is not right because the default for numeric types is 0, not 1. The num::One trait would be more appropriate.


#8

To wit:

extern crate num_traits;
use num_traits::One;

fn multiplier<'a, I, T>(vals: I) -> T 
    where I: Iterator<Item=&'a T>,
          T: 'a + One + Copy,
{
    vals.fold(One::one(), |acc, &val| acc * val)
}

fn main() {
    let data = vec![1.0, 2.0, 3.0, 4.0];
    println!("{}", multiplier(data.iter())); // 24
}