rustiteratormap-functionfilterfunctionrust-result

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


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?


Solution

  • 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