rust

Avoid turbofish type anotation on collect() to Result<Vec<_>, _>


I would like to use the collect() an iterator of Result<T, E>s into a Vec<T> and early-return in case any of the elements in the iterator was an error. So, something like this:

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let input = ""; // Snip real input
    let games: Vec<Game> = input.lines().map(parse_game).collect()?;
    println!("parsed {} games", games.len());
    Ok(())
}

struct Game;

fn parse_game(_s: &str) -> Result<Game, Box<dyn Error>> {
    Ok(Game)
}

(Playground link, and yes, the code is inspired on Advent of Code 2023 day 2)

But this doesn't work, it fails to compile with the error:

error[E0282]: type annotations needed
 --> src/main.rs:5:58
  |
5 |     let games: Vec<Game> = input.lines().map(parse_game).collect()?;
  |                                                          ^^^^^^^ cannot infer type of the type parameter `B` declared on the method `collect`
  |
help: consider specifying the generic argument
  |
5 |     let games: Vec<Game> = input.lines().map(parse_game).collect::<Vec<_>>()?;
  |                                                                 ++++++++++

If I add the type annotation to use the correct impl FromIterator on collect(), it works:

let games: Vec<Game> = input.lines().map(parse_game).collect::<Result<_, _>>()?;

(Playground link)

But I don't understand why this turbofish type annotation of ::<Result<_, _>> is necessary. And I find it quite superfluous and noisy.

I would have expected the Rust compiler to know that parse_game returns Results, and that if I want to collect into a Vec<Game> (as specified by the games variable type) and I'm early-returning with the ? operator on a function that returns Result (main()), then that collect() call should be returning Result too and thus wouldn't need an explicit type annotation.

So, is there a way to get rid of this turbofish type annotation? Or some other idiomatic way of expressing this?


Solution

  • There are ways to get rid of the turbofish, but you're not going to like them. First way is to use an intermediate variable.

    let games_result: Result<_, _> = input.lines().map(parse_game).collect();
    let games: Vec<Game> = games_result?;
    

    Second way is to use collect's internal trait function, FromIterator::from_iter.

    let games: Vec<Game> = Result::from_iter(input.lines().map(parse_game))?;
    

    The first one doesn't offer any improvements over the turbofish in noisiness, and the second one loses the functional style of collect.

    Unfortunately, I don't think there's a better way of avoiding the turbofish for collect. However, the itertools crate offers try_collect, which does what you want.

    use itertools::Itertools;
    let games: Vec<Game> = input.lines().map(parse_game).try_collect()?;