Compiler unable to infer type for a simple map() [Why?]

Note that the below example is not idiomatic and can be written more cleanly. However, it has been constructed to elicit responses. I am trying to understand what is happening behind the scenes that the compiler is unable to infer the type for a seemingly simple map() (followed by collect()) expression?

(The expression under-consideration is on the second from last line.)

#[derive(Debug)]
struct Rec {
    a: String,
    b: i32,
}

impl Rec {
    fn from_row(row: &(String, i32)) -> Rec {
        Rec {
            a: row.0.clone(),
            b: row.1,
        }
    }
}

pub fn main() {
    let rows = [
        (String::from("Apple"), 16),
        (String::from("Tomato"), 18),
        (String::from("Potato"), 24),
    ];
    
    // cannot infer type (for res) 
    // consider giving `res` a type
    let res = (0..rows.len()).map(|i| Rec::from_row(&rows[i]))
       .collect();
    println!("{:?}", res);
}

It works, when we add a type for the map expression.

 let res: Vec<Rec> = (0..rows.len()).map(|i| Rec::from_row(&rows[i]))
    .collect();

While the documentation for collect() does note the following about type-inference, this particular case seems to be pretty simple to cause problems with inference.

Because collect() is so general, it can cause problems with type inference. As such, collect() is one of the few times you'll see the syntax affectionately known as the 'turbofish': ::<> . This helps the inference algorithm understand specifically which collection you're trying to collect into.

As you correctly guessed in a later edit, the problem is not map(), but collect(), which you can cross-check by removing the collect().

The reason why type inference fails in this case is that you can collect iterators into various types of collections. For example, in your case, collecting into a HashMap<String, i32> would work just as well. As a result, collect() without type annotations is ambiguous, and for now rustc errs on the side of caution in this ambiguous case by rejecting the program.

As an aside, note that type annotations are not the only way to specify that you want a Vec. Sending res to a function that expects a Vec also works.

In the future, this could be improved by letting the compiler select a default collection type (most likely Vec) when an ambiguity arises. But doing so requires language-level changes, in the form of generic default type parameters becoming a default for type inference. Unfortunately, interactions with other type inference defaults make this problem harder to resolve than it may seem at first sight.

2 Likes

Thanks!

The link for generic default type parameters becoming a default for type inference and interactions with other type inference defaults seem to point to the same page. From the context, it seems like they could be two different pages.

Thanks for the pointer, fixed that.

1 Like