The solution with best performance is using Either
. It is similar to Cerberus's Vec
solution, except that Vec
is replaced by Left
and Right
, as mentioned in another question, for better performance.
fn to_either(
result: Result<Vec<MyStruct>, Error>,
) -> impl Iterator<Item = Result<MyStruct, Error>> {
match result {
Ok(vec) => Right(vec.into_iter().map(Ok)),
Err(e) => Left(std::iter::once(Err(e))),
}
}
fn either_collect(my_structs: &[MyStruct], err_value: u64) -> Result<Vec<MyStruct>, Error> {
my_structs
.iter()
.map(|my_struct| produce_result(&my_struct, err_value))
.flat_map(to_either)
.collect::<Result<Vec<MyStruct>, Error>>()
}
Also Box
can be used as mentioned in https://stackoverflow.com/a/29760740/955091 .
Below is an updated benchmark including all the solutions
#![feature(test)]
extern crate test;
use test::{black_box, Bencher};
use either::*;
struct Error;
struct MyStruct(u64);
fn produce_result(item: &MyStruct, err_value: u64) -> Result<Vec<MyStruct>, Error> {
if item.0 == err_value {
Err(Error)
} else {
Ok((0..item.0).map(MyStruct).collect())
}
}
fn to_box_iterator(
result: Result<Vec<MyStruct>, Error>,
) -> Box<dyn Iterator<Item = Result<MyStruct, Error>>> {
match result {
Ok(vec) => Box::new(vec.into_iter().map(Ok)),
Err(e) => Box::new(std::iter::once(Err(e))),
}
}
fn to_either(
result: Result<Vec<MyStruct>, Error>,
) -> impl Iterator<Item = Result<MyStruct, Error>> {
match result {
Ok(vec) => Left(vec.into_iter().map(Ok)),
Err(e) => Right(std::iter::once(Err(e))),
}
}
fn either_collect(my_structs: &[MyStruct], err_value: u64) -> Result<Vec<MyStruct>, Error> {
my_structs
.iter()
.map(|my_struct| produce_result(&my_struct, err_value))
.flat_map(to_either)
.collect::<Result<Vec<MyStruct>, Error>>()
}
fn box_collect(my_structs: &[MyStruct], err_value: u64) -> Result<Vec<MyStruct>, Error> {
my_structs
.iter()
.map(|my_struct| produce_result(&my_struct, err_value))
.flat_map(to_box_iterator)
.collect::<Result<Vec<MyStruct>, Error>>()
}
fn internal_collect(my_structs: &[MyStruct], err_value: u64) -> Result<Vec<MyStruct>, Error> {
my_structs
.iter()
.map(|my_struct| produce_result(&my_struct, err_value))
.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>>()
}
fn external_collect(my_structs: &[MyStruct], err_value: u64) -> Result<Vec<MyStruct>, Error> {
Ok(my_structs
.iter()
.map(|my_struct| produce_result(&my_struct, err_value))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect())
}
#[bench]
pub fn internal_collect_start_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| internal_collect(&my_structs, 0));
}
#[bench]
pub fn box_collect_start_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| box_collect(&my_structs, 0));
}
#[bench]
pub fn either_collect_start_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| either_collect(&my_structs, 0));
}
#[bench]
pub fn external_collect_start_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| external_collect(&my_structs, 0));
}
#[bench]
pub fn internal_collect_end_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| internal_collect(&my_structs, 999));
}
#[bench]
pub fn box_collect_end_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| box_collect(&my_structs, 999));
}
#[bench]
pub fn either_collect_end_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| either_collect(&my_structs, 999));
}
#[bench]
pub fn external_collect_end_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| external_collect(&my_structs, 999));
}
#[bench]
pub fn internal_collect_no_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| internal_collect(&my_structs, 1000));
}
#[bench]
pub fn box_collect_no_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| box_collect(&my_structs, 1000));
}
#[bench]
pub fn either_collect_no_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| either_collect(&my_structs, 1000));
}
#[bench]
pub fn external_collect_no_error(b: &mut Bencher) {
let my_structs: Vec<_> = black_box((0..1000).map(MyStruct).collect());
b.iter(|| external_collect(&my_structs, 1000));
}
Result:
cargo bench
Downloaded either v1.8.1
Downloaded 1 crate (16.0 KB) in 0.24s
Compiling either v1.8.1
Compiling my-project v0.1.0 (/home/runner/DarksalmonLiquidPi)
Finished bench [optimized] target(s) in 4.63s
Running unittests src/main.rs (target/release/deps/my_project-03c6287d7b53ee50)
running 12 tests
test box_collect_end_error ... bench: 9,439,159 ns/iter (+/- 7,009,221)
test box_collect_no_error ... bench: 9,538,551 ns/iter (+/- 7,923,652)
test box_collect_start_error ... bench: 81 ns/iter (+/- 178)
test either_collect_end_error ... bench: 4,266,292 ns/iter (+/- 6,008,125)
test either_collect_no_error ... bench: 3,341,910 ns/iter (+/- 6,344,290)
test either_collect_start_error ... bench: 41 ns/iter (+/- 53)
test external_collect_end_error ... bench: 209,960 ns/iter (+/- 663,883)
test external_collect_no_error ... bench: 10,074,473 ns/iter (+/- 4,737,417)
test external_collect_start_error ... bench: 17 ns/iter (+/- 55)
test internal_collect_end_error ... bench: 8,860,670 ns/iter (+/- 6,148,916)
test internal_collect_no_error ... bench: 8,564,756 ns/iter (+/- 6,842,558)
test internal_collect_start_error ... bench: 44 ns/iter (+/- 165)
test result: ok. 0 passed; 0 failed; 0 ignored; 12 measured; 0 filtered out; finished in 69.52s
The benchmark can be found at https://replit.com/@Atry/DarksalmonLiquidPi