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>>();
}
( Детская площадка )
Ошибка:
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>`
Однако в вашем случае поведение отличается, поскольку Result
реализует IntoIterator
. Этот итератор возвращает Ok
значение без изменений и пропускает Err
, поэтому, когда flat_map
пропингует 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>>();
Детская площадка
Есть и другой способ, который может быть более эффективным, если ошибки действительно присутствуют (даже если только иногда):
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())
}
Детская площадка
Я быстро провел тестирование - код на детской площадке , хотя его нельзя запустить из-за отсутствия 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)
Кажется, что версия с двумя цепочками collect
s удваивает время метода с вложенными collect
s, если выполнение прошло успешно, но существенно (на единицу в-третьих, примерно) быстрее, когда выполнение делает короткое замыкание на какую-то ошибку. Этот результат согласуется с несколькими прогонами тестов, поэтому сообщаемая большая разница, вероятно, не имеет значения.