What's the most idiomatic way of working with an Iterator of Results?
Solution 1
You can implement these iterators yourself. See how filter
and map
are implemented in the standard library.
map_ok
implementation:
#[derive(Clone)]
pub struct MapOkIterator<I, F> {
iter: I,
f: F,
}
impl<A, B, E, I, F> Iterator for MapOkIterator<I, F>
where
F: FnMut(A) -> B,
I: Iterator<Item = Result<A, E>>,
{
type Item = Result<B, E>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|x| x.map(&mut self.f))
}
}
pub trait MapOkTrait {
fn map_ok<F, A, B, E>(self, func: F) -> MapOkIterator<Self, F>
where
Self: Sized + Iterator<Item = Result<A, E>>,
F: FnMut(A) -> B,
{
MapOkIterator {
iter: self,
f: func,
}
}
}
impl<I, T, E> MapOkTrait for I
where
I: Sized + Iterator<Item = Result<T, E>>,
{
}
filter_ok
is almost the same:
#[derive(Clone)]
pub struct FilterOkIterator<I, P> {
iter: I,
predicate: P,
}
impl<I, P, A, E> Iterator for FilterOkIterator<I, P>
where
P: FnMut(&A) -> bool,
I: Iterator<Item = Result<A, E>>,
{
type Item = Result<A, E>;
#[inline]
fn next(&mut self) -> Option<Result<A, E>> {
for x in self.iter.by_ref() {
match x {
Ok(xx) => if (self.predicate)(&xx) {
return Some(Ok(xx));
},
Err(_) => return Some(x),
}
}
None
}
}
pub trait FilterOkTrait {
fn filter_ok<P, A, E>(self, predicate: P) -> FilterOkIterator<Self, P>
where
Self: Sized + Iterator<Item = Result<A, E>>,
P: FnMut(&A) -> bool,
{
FilterOkIterator {
iter: self,
predicate: predicate,
}
}
}
impl<I, T, E> FilterOkTrait for I
where
I: Sized + Iterator<Item = Result<T, E>>,
{
}
Your code may look like this:
["1", "2", "3", "4"]
.iter()
.map(|x| x.parse::<u16>().map(|a| a + 10))
.filter_ok(|x| x % 2 == 0)
.map_ok(|x| x + 100)
.collect::<Result<Vec<_>, std::num::ParseIntError>>()
Solution 2
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 a Result<X<T>>
:
let results: Result<Vec<i32>, _> = result_i32_iter.collect();
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())
.
let results: Vec<i32> = result_i32_iter.scan((), |_, x| x.ok());
Note that these operations can be combined with earlier operations in many cases.
Solution 3
Since Rust 1.27, Iterator::try_for_each
could be of interest:
An iterator method that applies a fallible function to each item in the iterator, stopping at the first error and returning that error.
This can also be thought of as the fallible form of
for_each()
or as the stateless version oftry_fold()
.
Solution 4
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 for Result
either unfortunately. I think your example is as idiomatic as it could get, but here are some small improvements:
let things = vec![...]; // e.g. Vec<String>
things.iter().map(|thing| {
// The ? operator can be used in place of try! in the nightly version of Rust
let a = do_stuff(thing)?;
Ok(other_stuff(a))
// The closure braces can be removed if the code is a single expression
}).filter(|thing_result| match *thing_result {
Err(e) => true,
Ok(a) => check(a),
}
).map(|thing_result| {
let a = thing_result?;
// do stuff
b
})
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 return Some(x)
instead of true, and None
instead of false, you can use filter_map
:
let bar = things.iter().filter_map(|thing| {
match do_stuff(thing) {
Err(e) => Some(Err(e)),
Ok(a) => {
let x = other_stuff(a);
if check_2(x) {
Some(Ok(x))
} else {
None
}
}
}
}).map(|thing_result| {
let a = try!(thing_result);
// do stuff
b
}).collect::<Result<Vec<_>, _>>();
You can get rid of the let a = try!(thing);
by using a match in some cases as well. However, using filter_map
here doesn't seem to help.
Tim McLean
Software engineer. I focus on the practical application of cryptography and information security to software development. If you like what I write here, check out my website for more! Formerly known as user595228.
Updated on May 04, 2020Comments
-
Tim McLean almost 4 years
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?
-
ArtemGr about 8 years
- In the filter's closure, I have to use
-
aSpex about 8 years
vec![Ok(1),Ok(2),Err(3),Ok(4)].into_iter()....
looks better but creating a vector from the array is more expencive -
Per Lundberg almost 7 yearsUpvoted for recommending the
filter_map
method. Wasn't aware of it, it's great! -
goertzenator almost 6 yearsAn alternative to
scan
as used above would betake_while(Result::is_ok).map(Result::unwrap)
-
Veedrac almost 6 years@goertzenator Best avoid panic-y code if there's an alternative, though.
-
Tails almost 4 yearsI was buckling up to write a reducer that would return the first error result or the complete vector... but
.collect()
is amazing!