如何将一个 Vec<Result<T, E>> 转换为 Result<Vec<T>, E>?

53
有没有更优雅、更有见解的方法将 Vec<Result<T, E>> 转换为 Result<Vec<T>, E>?如果向量中所有值都是 Ok<T>,则希望获得 Ok<Vec<T>>;如果至少有一个值是 Err<E>,则希望获得 Err<E>

示例:

fn vec_of_result_to_result_of_vec<T, E>(v: Vec<Result<T, E>>) -> Result<Vec<T>, E>
where
    T: std::fmt::Debug,
    E: std::fmt::Debug,
{
    let mut new: Vec<T> = Vec::new();

    for el in v.into_iter() {
        if el.is_ok() {
            new.push(el.unwrap());
        } else {
            return Err(el.unwrap_err());
        }
    }

    Ok(new)
}

我希望有一个更加声明式的方式来编写这个函数。该功能强制我编写永远不会被使用的where从句,而Err(el.unwrap_err())看起来毫无意义。换句话说,代码为了让编译器高兴而做了很多事情。我觉得这是一个非常普遍的情况,应该有更好的方法来解决它。

1
你可以使用 match 代替 is_ok() 来避免 unwrap()(和 Debug 边界)的出现。但在这种情况下,使用 collect() 更加简洁。 - Tavian Barnes
2个回答

58

Result<Vec<T>, E>可以直接使用collect()方法遍历Result<T, E>;也就是说,你的整个函数可以被以下代码替换:

let new: Result<Vec<T>, E> = v.into_iter().collect()

1
你能解释一下这个行为吗?我不知道为什么收集一个迭代器会从其内容中删除Result结构类型。 - Tobi
3
@Tobi 迭代器上的 collect 方法(https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect)适用于实现了 FromIterator 特质的任何类型。正如 @chpio 所述,对于 Result 的迭代器,已经有了 FromIterator 的实现。 - alightgoesout
这里的collect方法非常灵活。v.into_iter().collect::<Vec<_>>()返回一个结果向量。v.into_iter().collect::<Result<Vec<_>,_>>()返回一个向量结果。如果编译器能够确定你想要哪一个,那么你就不需要使用“turbo fish”了。 - Troy Daniels
结果的右侧会发生什么呢?可以推测遇到的第一个错误会被返回(有点像短路的方式)?这似乎类似于Scala中的函数式库cats中的遍历,但我相信错误会通过将它们彼此“折叠”在一起来收集在一起。 - Oli

7
你可以使用 Result 上的 FromIterator 特质实现.collect() 需要目标类型上的 FromIterator 并调用它来从 Iterator 进行转换):
fn vec_of_result_to_result_of_vec<T, E>(v: Vec<Result<T, E>>) -> Result<Vec<T>, E> {
    v.into_iter().collect()
}

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