如何解压缩一个 Result<(A, B), E> 序列为 (Vec<A>, Vec<B>),并在首次错误时停止?

9
我想要做像这样的事情:
enum MyEnum {
    Foo(Vec<Stuff>),
    // ...
}

impl MyEnum {
    fn do_sth(&self) -> Result<Bar, E> {
        match *self {
            MyEnum::Foo(ref vec) => {
                let (a, b): (Vec<A>, Vec<B>) = vec
                    .iter()
                    .map(|thing| thing.make_a_tuple(arg)) // returns Result<(A, B), E>
                    .collect::<Result<_, _>>()? // stop on first error
                    .unzip();

                // use a and b
            }

            // other cases
        }

        // ...
    }
}

使用此代码时无法编译,会出现错误:在此上下文中必须知道该值的类型。经过多次试验,我将其编译成功了,代码如下:

    fn do_sth(&self) -> Result<Bar, E> {
        match *self {
            MyEnum::Foo(ref vec) => {
                let (a, b): (Vec<A>, Vec<B>) = vec
                    .iter()
                    .map(|thing| thing.make_a_tuple(arg))
                    .collect::<Result<Vec<_>, _>>()?
                    .into_iter()
                    .unzip();

                // use a and b
            }

            // other cases
        }
    }

然而,我希望避免不必要地分配Vec<(A, B)>

不使用中间分配,这可行吗?我相信我可以用循环自己实现,但我更喜欢学习Rust的方法。


“我不完全理解为什么在收集Vec<Result<_, _>>时collect会做正确的事情。”这将成为一个单独的问题,幸运的是已经在这里得到了答案(https://dev59.com/kV8d5IYBdhLWcg3w21Om#26370894)。 - E net4
关于这个问题,你确实遇到了一个有趣的挑战。unzip 的行为与 collect 相当不同:根据 API 明确规定,unzip 将创建两个空容器,并使用 extend 将数据推入它们。这意味着它无法对 Result 进行特殊处理。 - E net4
这确实很有帮助,谢谢。但我能否将其收集到类似于Result<SomeIterator,E>的东西中?这样做甚至有意义吗?编辑:当我发布回复时,您的第二条评论出现在我的屏幕上。我想我会用手写循环来保持简单。 - novice-rusticean
1个回答

7
我经常遇到迭代器Result的这种问题,因此我在标准库中添加了一个(私有)帮助类型,名为ResultShunt。 这为ResultSumProduct实现提供支持。
我还将其提交给itertools以便重复使用:
use itertools; // 0.10.1

fn main() {
    let iter_source: Vec<Result<(i32, i32), bool>> = vec![Ok((1, 2)), Err(false), Ok((3, 4))];

    let z = itertools::process_results(iter_source, |iter| iter.unzip::<_, _, Vec<_>, Vec<_>>());

    println!("{:?}", z);
}

1
这真的起了作用!另外感谢你指导我使用itertools。我甚至不知道那个包存在。 - novice-rusticean

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