and then sum()
knows to return an i32
This is the key missing point. While the "input" type is already known (it has to be something that implements Iterator
in order for sum
to even be available), the "output" type is very flexible.
Check out Iterator::sum
:
fn sum<S>(self) -> S
where
S: Sum<Self::Item>,
It returns a generic type S
which has to implement Sum
. S
does not have to match Self::Item
. Therefore, the compiler requires you to specify what
type to sum into.
Why is this useful? Check out these two sample implementations from the standard library:
impl Sum<i8> for i8
impl<'a> Sum<&'a i8> for i8
That's right! You can sum up an iterator of u8
or an iterator of &u8
! If we didn't have this, then this code wouldn't work:
fn main() {
let a: i32 = (0..5).sum();
let b: i32 = [0, 1, 2, 3, 4].iter().sum();
assert_eq!(a, b);
}
As bluss points out, we could accomplish this by having an associated type which would tie u8 -> u8
and &'a u8 -> u8
.
If we only had an associated type though, then the target sum type would always be fixed, and we'd lose flexibility. See When is it appropriate to use an associated type versus a generic type? for more details.
As an example, we can also implement Sum<u8>
for our own types. Here, we sum up u8
s, but increase the size of the type we are summing, as it's likely the sum would exceed a u8
. This implementation is in addition to the existing implementations from the standard library:
#[derive(Debug, Copy, Clone)]
struct Points(i32);
impl std::iter::Sum<u8> for Points {
fn sum<I>(iter: I) -> Points
where
I: Iterator<Item = u8>,
{
let mut pts = Points(0);
for v in iter {
pts.0 += v as i32;
}
pts
}
}
fn main() {
let total: Points = (0u8..42u8).sum();
println!("{:?}", total);
}