Как обработать результат в flat_map - PullRequest
2 голосов
/ 22 января 2020

Я знаю, что мы можем использовать collect для перемещения Result от внутреннего к внешнему, например:

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>>;

, который распространяет ошибку от замыкания к внешнему.

Однако этот метод не работает в 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>>;

компилятор жалуется: «коллекция типа std::result::Result<std::vec::Vec<MyStruct>, Error> не может быть построена из итератора над элементами типа std::vec::Vec<MyStruct> "

Как обойти этот случай?

1 Ответ

2 голосов
/ 22 января 2020

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, если выполнение прошло успешно, но существенно (на единицу в-третьих, примерно) быстрее, когда выполнение делает короткое замыкание на какую-то ошибку. Этот результат согласуется с несколькими прогонами тестов, поэтому сообщаемая большая разница, вероятно, не имеет значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...