我知道我们可以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>”
如何解决这种情况?
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的方法花费两倍的时间,但是当执行在某些错误上发生短路时,速度明显(大约三分之一)快。这个结果在多次基准测试中是一致的,所以报告的大差异可能并不重要。
使用itertools的flatten_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)
| 归档时间: |
|
| 查看次数: |
2045 次 |
| 最近记录: |