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<_, _>>()?;
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 Result
s, 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?
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()?;