I have code like this:
let things = vec![/* ...*/]; // e.g. Vec<String>
things
.map(|thing| {
let a = try!(do_stuff(thing));
Ok(other_stuff(a))
})
.filter(|thing_result| match *thing_result {
Err(e) => true,
Ok(a) => check(a),
})
.map(|thing_result| {
let a = try!(thing_result);
// do stuff
b
})
.collect::<Result<Vec<_>, _>>()
In terms of semantics, I want to stop processing after the first error.
The above code works, but it feels quite cumbersome. Is there a better way? I've looked through the docs for something like filter_if_ok
, but I haven't found anything.
I am aware of collect::<Result<Vec<_>, _>>
, and it works great. I'm specifically trying to eliminate the following boilerplate:
- In the filter's closure, I have to use
match
onthing_result
. I feel like this should just be a one-liner, e.g..filter_if_ok(|thing| check(a))
. - Every time I use
map
, I have to include an extra statementlet a = try!(thing_result);
in order to deal with the possibility of anErr
. Again, I feel like this could be abstracted away into.map_if_ok(|thing| ...)
.
Is there another approach I can use to get this level of conciseness, or do I just need to tough it out?
filter_map
can be used to reduce simple cases of mapping then filtering. In your example there is some logic to the filter so I don't think it simplifies things. I don't see any useful functions in the documentation forResult
either unfortunately. I think your example is as idiomatic as it could get, but here are some small improvements:The
?
operator can be less readable in some cases, so you might not want to use it.If you are able to change the
check
function to returnSome(x)
instead of true, andNone
instead of false, you can usefilter_map
:You can get rid of the
let a = try!(thing);
by using a match in some cases as well. However, usingfilter_map
here doesn't seem to help.There are lots of ways you could mean this.
If you just want to panic, use
.map(|x| x.unwrap())
.If you want all results or a single error,
collect
into aResult<X<T>>
:If you want everything except the errors, use
.filter_map(|x| x.ok())
or.flat_map(|x| x)
.If you want everything up to the first error, use
.scan((), |_, x| x.ok())
.Note that these operations can be combined with earlier operations in many cases.
You can implement these iterators yourself. See how
filter
andmap
are implemented in the standard library.map_ok
implementation:filter_ok
is almost the same:Your code may look like this:
playground
Since Rust 1.27,
Iterator::try_for_each
could be of interest: