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

ぐ巨炮叔叔 提交于 2019-11-30 13:53:18

问题


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?


回答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




回答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.




回答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().




回答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.



来源:https://stackoverflow.com/questions/36368843/whats-the-most-idiomatic-way-of-working-with-an-iterator-of-results

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!