【问题标题】:What's the most idiomatic way of working with an Iterator of Results? [duplicate]使用结果迭代器最惯用的方式是什么? [复制]
【发布时间】:2016-07-21 23:58:27
【问题描述】:

我有这样的代码:

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::&lt;Result&lt;Vec&lt;_&gt;, _&gt;&gt;,它工作得很好。我特别想消除以下样板:

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

我可以使用另一种方法来获得这种简洁程度,还是我只需要坚持下去?

【问题讨论】:

标签: rust iterator map-function filterfunction rust-result


【解决方案1】:

您可以通过多种方式表达这个意思。

如果您只想恐慌,请使用.map(|x| x.unwrap())

如果您想要所有结果单个错误,请将collect 转换为Result&lt;X&lt;T&gt;&gt;

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

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

【讨论】:

  • 赞成推荐 filter_map 方法。不知道,它太棒了
  • 上面使用的scan 的替代品是take_while(Result::is_ok).map(Result::unwrap)
  • @goertzenator 如果有替代方案,最好避免恐慌代码。
  • 我正准备写一个reducer,它会返回第一个错误结果或完整的向量......但是.collect()太棒了!
【解决方案2】:

从 Rust 1.27 开始,Iterator::try_for_each 可能会引起人们的兴趣:

一种迭代器方法,它对迭代器中的每个项目应用一个错误函数,在第一个错误处停止并返回该错误。

这也可以被认为是for_each() 的易错形式或try_fold() 的无状态版本。

【讨论】:

    【解决方案3】:

    您可以自己实现这些迭代器。看看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().... 看起来更好,但从数组创建向量更昂贵
    【解决方案4】:

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

    在某些情况下,您也可以通过使用匹配来摆脱 let a = try!(thing);。但是,在这里使用filter_map 似乎没有帮助。

    【讨论】:

      猜你喜欢
      • 2020-12-23
      • 2011-06-01
      • 2017-04-05
      • 2013-09-07
      • 1970-01-01
      • 2016-03-27
      • 1970-01-01
      • 2012-11-14
      • 2011-08-22
      相关资源
      最近更新 更多