When writing iterator chains, I often run into awkwardness in handling fallible steps along the chain. For example, I often want to write something like the following:
let files = vec!["foo.txt", "bar.txt", "baz.txt"];
let sum: i64 = files
.into_iter()
.map(File::open)
// do something to handle unopened files
.map(BufReader::new)
.flat_map(BufReader::lines)
// do something to handle unreadable lines
.map(|line| line.parse::<i64>())
// do something to handle bad parses
.sum();
println!("{}", sum);
If I want to ignore errors entirely, this is easy: all of the // do something
lines can be .filter_map(Result::ok)
. But I find that the behavior I want is often to iterate down the chain, stopping at the first Err
value, and returning that error. The best idiom I’ve seen for this is the following:
let mut open_err = Ok(());
let mut read_err = Ok(());
let mut parse_err = Ok(());
let files = vec!["foo.txt", "bar.txt", "baz.txt"];
let sum: i64 = files
.into_iter()
.map(File::open)
.map(|res| res.map_err(|e| open_err = Err(e)))
.filter_map(Result::ok)
.map(BufReader::new)
.flat_map(BufReader::lines)
.map(|res| res.map_err(|e| read_err = Err(e)))
.filter_map(Result::ok)
.map(|line| line.parse::<i64>())
.map(|res| res.map_err(|e| parse_err = Err(e)))
.filter_map(Result::ok)
.sum();
open_err?;
read_err?;
parse_err?;
println!("{}", sum);
Ok(())
There are a few problems with this approach. First and foremost, you have to manually check the Result
s, and if you don’t you print an incorrect partial sum. Secondly, this error handling pattern is pretty hard to discover (I’m having trouble just finding the blog post where I originally found it), and it’s pretty noisy. I don’t have a good solution to the first issue (and would love to see one), but to help noise and discoverability I propose an iterator adaptor PutErr
which does the .map(|res| res.map_err(|e| parse_err = Err(e))).filter_map(Result::ok)
part. With this, the above chain looks like this:
let sum: i64 = files
.into_iter()
.map(File::open)
.put_err(&mut open_err)
.map(BufReader::new)
.flat_map(BufReader::lines)
.put_err(&mut read_err)
.map(|line| line.parse::<i64>())
.put_err(&mut parse_err)
.sum();
I’ve implemented this as an addition to bluss’s itertools
crate here, but before submitting it there I thought I would see if the libs team had any interest in adding something like this to the standard library. So, what do you think? My biggest concern is the need to manually check the Result
s, so I’d be interested if anyone has come up with a better idiom there.