Using ? inside closure to return Result

Here is some code I am working on for a personal project. I would consider myself a Rust beginner.

pub fn parse_raw(raw: HashMap<String, String>) -> Result<HashMap<KeyCode, Command>, Error> {
	Ok(
		raw
			.into_iter()
			.map(|(key, cmd)| {
				(
					keycode_from_str(&key)?,
					Command::from_str(&cmd)?,
				)
			})
			.collect()
	)
}

This does not compile with "value of type HashMap<KeyCode, keys::Command> cannot be built from std::iter::Iterator<Item=(Result<KeyCode, std::io::Error>, Result<keys::Command, std::io::Error>)>".
I want to pass the errors that might occur when parsing the key and value of the hashmap as a result to the entire function, i.e. if a key parsing fails then return that and if a value parsing fails then return that (although I could change that code so that the value parsing can never fail if that makes it easier).

There are two problems here.

The first problem is that the closure passed to Iterator::map() is itself a function which you are writing code inside, so the ?s are applying to that function, not the outer function (and in fact that's the only thing they can do; there's no non-local control flow in Rust that could reasonably be used to exit the outer function from inside map().) So, you will need to make the closure itself a well-typed Result-returning function. That's simple; just add Ok() around the return value.

		.map(|(key, cmd)| {
			Ok((
				keycode_from_str(&key)?,
				Command::from_str(&cmd)?,
			))
		})

Now, you have the second problem (which is actually the one the error message you quoted is about): you have an iterator of Results. However, this is so common that the standard library has built-in support for this in collect() (or rather the FromIterator trait it uses), and all you need to do to take advantage of that is remove the Ok() from the outer part of your function, so that you're collecting into the Result<HashMap<... directly. The collect() will automatically stop and return Err when it sees the first Err item from the iterator.

pub fn parse_raw(raw: HashMap<String, String>) -> Result<HashMap<KeyCode, Command>, Error> {
	raw
		.into_iter()
		.map(|(key, cmd)| {
			Ok((
				keycode_from_str(&key)?,
				Command::from_str(&cmd)?,
			))
		})
		.collect()
}

Playground

7 Likes

Thanks, works like a charm, great explanation!