如何处理 flat_map 中的结果

Evi*_*ian 10 rust

我知道我们可以collect用来将 aResult从内部移动到外部,例如:

fn produce_result(my_struct: &MyStruct) -> Result<MyStruct, Error>;

let my_results: Vec<MyStruct> = vec![];
let res = my_results.iter().map(|my_struct| produce_result(&my_struct)).collect::<Result<Vec<MyStruct>, Error>>;
Run Code Online (Sandbox Code Playgroud)

它将错误从闭包传播到外部。

但是,这种方法在以下flat_map情况下不起作用(Rust playground):

fn produce_result(my_struct: &MyStruct) -> Result<Vec<MyStruct>, Error>;

let my_results: Vec<MyStruct> = vec![];
let res = my_results.iter().flat_map(|my_struct| produce_result(&my_struct)).collect::<Result<Vec<MyStruct>, Error>>;
Run Code Online (Sandbox Code Playgroud)

编译器抱怨:“std::result::Result<std::vec::Vec<MyStruct>, Error>不能从类型元素的迭代器构建类型集合std::vec::Vec<MyStruct>

如何解决这种情况?

Cer*_*rus 7

flat_map通过调用其IntoIterator实现,“扁平化”从闭包返回的值的顶层。重要的是它不会试图进入内部- 即,如果你有自己的MyResult,它会自己出错flat_map

enum Error {}

enum MyResult<T, U> {
    Ok(T),
    Err(U),
}

struct MyStruct;

fn produce_result(item: &MyStruct) -> MyResult<Vec<MyStruct>, Error> {
    MyResult::Ok(vec![])
}

fn main() {
    let my_structs: Vec<MyStruct> = vec![];
    let res = my_structs
        .iter()
        .flat_map(|my_struct| produce_result(&my_struct))
        .collect::<Result<Vec<MyStruct>, Error>>();
}
Run Code Online (Sandbox Code Playgroud)

游乐场

错误:

error[E0277]: `MyResult<std::vec::Vec<MyStruct>, Error>` is not an iterator
  --> src/main.rs:18:10
   |
18 |         .flat_map(|my_struct| produce_result(&my_struct))
   |          ^^^^^^^^ `MyResult<std::vec::Vec<MyStruct>, Error>` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `MyResult<std::vec::Vec<MyStruct>, Error>`
   = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `MyResult<std::vec::Vec<MyStruct>, Error>`
Run Code Online (Sandbox Code Playgroud)

但是,在您的情况下,行为是不同的,因为Result实现了IntoIterator. 这个迭代器产生的Ok值不变并跳过Err,所以当flat_mapping 时Result,你有效地忽略了每个错误,只使用成功调用的结果。

有一种方法可以修复它,虽然很麻烦。您应该明确匹配Result,将Err案例包装在 中并将案例Vec“分发”Ok到已经存在的Vec,然后让其flat_map工作:

let res = my_structs
    .iter()
    .map(|my_struct| produce_result(&my_struct))
    .flat_map(|result| match result {
        Ok(vec) => vec.into_iter().map(|item| Ok(item)).collect(),
        Err(er) => vec![Err(er)],
    })
    .collect::<Result<Vec<MyStruct>, Error>>();
Run Code Online (Sandbox Code Playgroud)

操场

还有另一种方法,如果确实存在错误(即使只是有时),它的性能可能会更高:

fn external_collect(my_structs: Vec<MyStruct>) -> Result<Vec<MyStruct>, Error> {
    Ok(my_structs
        .iter()
        .map(|my_struct| produce_result(&my_struct))
        .collect::<Result<Vec<_>, _>>()?
        .into_iter()
        .flatten()
        .collect())
}
Run Code Online (Sandbox Code Playgroud)

操场

我做了一些快速的基准测试——代码也在操场上,虽然由于没有cargo bench命令而无法在那里运行,所以我在本地运行了它们。结果如下:

test vec_result::external_collect_end_error   ... bench:   2,759,002 ns/iter (+/- 1,035,039)
test vec_result::internal_collect_end_error   ... bench:   3,502,342 ns/iter (+/- 438,603)

test vec_result::external_collect_start_error ... bench:          21 ns/iter (+/- 6)
test vec_result::internal_collect_start_error ... bench:          30 ns/iter (+/- 19)

test vec_result::external_collect_no_error    ... bench:   7,799,498 ns/iter (+/- 815,785)
test vec_result::internal_collect_no_error    ... bench:   3,489,530 ns/iter (+/- 170,124)
Run Code Online (Sandbox Code Playgroud)

如果执行成功,带有两个链接的collects的版本似乎比带有嵌套collects的方法花费两倍的时间,但是当执行在某些错误上发生短路时,速度明显(大约三分之一)快。这个结果在多次基准测试中是一致的,所以报告的大差异可能并不重要。


Nik*_*iki 5

使用itertoolsflatten_ok方法:

use itertools::Itertools;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // without error
    let res = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
        .into_iter()
        .map(Ok::<_, &str>)
        .flatten_ok()
        .collect::<Result<Vec<_>, _>>()?;
    println!("{:?}", res);

    // with error
    let res = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
        .into_iter()
        .map(|_| Err::<[i32; 2], _>("errored out"))
        .flatten_ok()
        .collect::<Result<Vec<_>, _>>()?;
    println!("{:?}", res); // not printed

    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接