What's the most idiomatic way of working with an Iterator of Results?

28,837

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>>()

playground

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 of try_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.

Share:
28,837
Tim McLean
Author by

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, 2020

Comments

  • Tim McLean
    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 on thing_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 statement let a = try!(thing_result); in order to deal with the possibility of an Err. 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?

  • aSpex
    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
    Per Lundberg almost 7 years
    Upvoted for recommending the filter_map method. Wasn't aware of it, it's great!
  • goertzenator
    goertzenator almost 6 years
    An alternative to scan as used above would be take_while(Result::is_ok).map(Result::unwrap)
  • Veedrac
    Veedrac almost 6 years
    @goertzenator Best avoid panic-y code if there's an alternative, though.
  • Tails
    Tails almost 4 years
    I was buckling up to write a reducer that would return the first error result or the complete vector... but .collect() is amazing!