HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMinor

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

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
idiomaticwithiteratortheworkingwaywhatmostresults

Problem

I have code like this:

let things = vec![/* ...*/]; // e.g. Vec
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::, _>>()


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::, _>>, 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 {
    iter: I,
    f: F,
}

impl Iterator for MapOkIterator
where
    F: FnMut(A) -> B,
    I: Iterator>,
{
    type Item = Result;

    #[inline]
    fn next(&mut self) -> Option {
        self.iter.next().map(|x| x.map(&mut self.f))
    }
}

pub trait MapOkTrait {
    fn map_ok(self, func: F) -> MapOkIterator
    where
        Self: Sized + Iterator>,
        F: FnMut(A) -> B,
    {
        MapOkIterator {
            iter: self,
            f: func,
        }
    }
}

impl MapOkTrait for I
where
    I: Sized + Iterator>,
{
}


filter_ok is almost the same:

#[derive(Clone)]
pub struct FilterOkIterator {
    iter: I,
    predicate: P,
}

impl Iterator for FilterOkIterator
where
    P: FnMut(&A) -> bool,
    I: Iterator>,
{
    type Item = Result;

    #[inline]
    fn next(&mut self) -> Option> {
        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(self, predicate: P) -> FilterOkIterator
    where
        Self: Sized + Iterator>,
        P: FnMut(&A) -> bool,
    {
        FilterOkIterator {
            iter: self,
            predicate: predicate,
        }
    }
}

impl FilterOkTrait for I
where
    I: Sized + Iterator>,
{
}


Your code may look like this:

["1", "2", "3", "4"]
    .iter()
    .map(|x| x.parse::().map(|a| a + 10))
    .filter_ok(|x| x % 2 == 0)
    .map_ok(|x| x + 100)
    .collect::, std::num::ParseIntError>>()


playground

Code Snippets

#[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>>,
{
}
#[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>>,
{
}
["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>>()

Context

Stack Overflow Q#36368843, score: 16

Revisions (0)

No revisions yet.