如何最通俗易懂地使用“结果迭代器”?

53

我有这样的代码:

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

从语义上讲,我想在第一个错误后停止处理。

上述代码可以工作,但感觉相当繁琐。有更好的方法吗?我查看了一些文档,比如filter_if_ok,但没有找到任何东西。

我知道collect::<Result<Vec<_>, _>>,它非常好用。我特别想消除以下样板代码:

  • 在过滤器的闭包中,我必须使用thing_resultmatch。我觉得这应该只是一个一行代码,例如.filter_if_ok(|thing| check(a)).
  • 每次使用map时,必须包含一个额外的语句let a = try!(thing_result);以处理Err的可能性。同样,我觉得这可以抽象为.map_if_ok(|thing| ...)

是否有其他方法可以达到这种简洁程度,还是我只需要坚持下去?


如何处理迭代器中的结果? - ArtemGr
给评论者们:即使这个问题不是重复的,但它是基于观点的,应该保持关闭状态。 - TylerH
4个回答

102

这句话有许多不同的解释。

如果您只是想要恐慌,可以使用.map(|x| x.unwrap())

如果您需要全部结果或仅一个错误,请将其collectResult<X<T>>

let results: Result<Vec<i32>, _> = result_i32_iter.collect();

如果你想要除了错误之外的所有内容,使用 .filter_map(|x| x.ok()).flat_map(|x| x)

如果你想要第一个错误之前的所有内容,使用 .scan((), |_, x| x.ok())

let results: Vec<i32> = result_i32_iter.scan((), |_, x| x.ok());

请注意,在许多情况下,这些操作可以与之前的操作相结合。


3
感谢推荐“filter_map”方法,因为不知道它的存在,这个方法真的太好用了! - Per Lundberg
4
作为上面所使用的 scan 的替代方案,可以使用 take_while(Result::is_ok).map(Result::unwrap) - goertzenator
2
最好避免使用紧急代码,如果有替代方案的话。 - Veedrac
2
我正准备编写一个reducer,它将返回第一个错误结果或完整的向量...但是.collect()太棒了! - Tails

23
自从 Rust 1.27 版本以来,可能会对 Iterator::try_for_each 感兴趣:

一个迭代器方法,在迭代器中的每个项上应用可失败函数,如果出现错误则停止并返回该错误。

这也可以被视为 for_each() 的可失败形式,或者是 try_fold() 的无状态版本。


15
你可以自己实现这些迭代器。请查看标准库中filtermap的实现。 map_ok实现方式:
#[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 几乎相同:

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

playground


vec![Ok(1),Ok(2),Err(3),Ok(4)].into_iter().... 看起来更好,但从数组创建向量的成本更高。 - aSpex

3

filter_map可以用于简化映射和过滤的简单情况。在您的示例中,过滤器中有一些逻辑,因此我认为它并没有简化事情。不幸的是,文档中也没有有用的Result函数。我认为您的示例已经是惯用的了,但这里有一些小改进:

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

? 操作符在某些情况下可读性较差,所以您可能不想使用它。

如果您能够更改 check 函数,使其返回 Some(x) 而不是 true,以及 None 而不是 false,则可以使用 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<_>, _>>();

您可以在某些情况下使用match来消除let a = try!(thing);, 但在这种情况下使用filter_map似乎并没有帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接