Why can't Rust infer the resulting type of Ite

2020-01-24 05:26发布

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?

2条回答
Bombasti
2楼-- · 2020-01-24 06:20

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();).

查看更多
Luminary・发光体
3楼-- · 2020-01-24 06:20

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);
}
查看更多
登录 后发表回答