Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't Rust infer the resulting type of Iterator::sum?

Tags:

This code works:

fn main() {     let a: i32 = (1i32..10).sum();     let b = a.pow(2); } 

If I remove the i32 type from a, then I get this error:

rustc 1.13.0 (2c6933acc 2016-11-07) error: the type of this value must be known in this context  --> <anon>:3:13   | 5 |     let b = a.pow(2);   |             ^^^^^^^^ 

Run the example

I would have expected that Rust turns (1i32..10) into an i32 iterator and then sum() knows to return an i32. What am I missing?

like image 684
Timmmm Avatar asked Dec 07 '16 12:12

Timmmm


2 Answers

The way sum is defined, the return value is open-ended; more than one type can implement the trait Sum<i32>. Here's an example where different types for a are used, both of which compile:

#[derive(Clone, Copy)] struct Summer {     s: isize, }  impl Summer {     fn pow(&self, p: isize) {         println!("pow({})", p);     } }  impl std::iter::Sum<i32> for Summer {     fn sum<I>(iter: I) -> Self     where         I: Iterator<Item = i32>,     {         let mut result = 0isize;         for v in iter {             result += v as isize;         }         Summer { s: result }     } }  fn main() {     let a1: i32 = (1i32..10).sum();     let a2: Summer = (1i32..10).sum();     let b1 = a1.pow(2);     let b2 = a2.pow(2); } 

Playground

Since both result types are possible, the type cannot be inferred and must be explicitly specified, either by a turbofish (sum::<X>()) or as the result of the expression (let x: X = ...sum();).

like image 53
Chris Emerson Avatar answered Sep 18 '22 05:09

Chris Emerson


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 u8s, 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); } 
like image 22
Shepmaster Avatar answered Sep 19 '22 05:09

Shepmaster