Вызов карты на Iter of Results в Rust

Я хотел бы написать код в стиле функционального программирования.

Однако я начинаю с итератора результатов и хочу применить эту функцию только к Ok элементам. Кроме того, я хочу остановить итерацию при первой ошибке (однако я был бы открыт для другого поведения).

Пока что я использую вложенный шаблон map(): <iter>.map(|l| l.map(replace)). Я считаю это крайне некрасивым.

Используя nightly result_flattening, я могу сгладить каждый вложенный Result<Result<T, E>, E> в Result<T, E>. Используя eyre::Context, я конвертирую различные типы ошибок в ошибки типа eyre::Report. Все это выглядит довольно неуклюже.

Как можно элегантно написать это в Rust?

Минимальный рабочий пример

#![feature(result_flattening)]
use std::io::BufRead;

use eyre::Context;

fn main() {
    let data = std::io::Cursor::new(b"FFBFFFBLLL\nBFBFBBFRLR\nFFFBFFBLLL");

    let seats: Result<Vec<_>, _> = data
        .lines()
        .map(|l| l.map(replace).context("force eyre"))
        .map(|l| l.map(|s| u32::from_str_radix(&s, 2).context("force eyre")))
        .map(|r| r.flatten())
        .collect();

    println!("{:#?}", seats);
}

fn replace(line: String) -> String {
    line.replace('F', "0")
        .replace('B', "1")
        .replace('L', "0")
        .replace('R', "1")
}

Дополнительные ссылки:


person Unapiedra    schedule 05.12.2020    source источник


Ответы (1)


Поскольку вы все равно отбрасываете тип ошибки, вы можете полностью избежать eyre и использовать .ok для преобразования Result в Option, а затем просто работать с and_then Option, чтобы каждый раз не сглаживать:

let seats: Option<Vec<_>> = data
    .lines()
    .map(|l| l.ok())
    .map(|l| l.map(replace))
    .map(|l| l.and_then(|s| u32::from_str_radix(&s, 2).ok()))
    // if you want to keep chaining
    .map(|l| l.and_then(|s| some_result_function(&s).ok()))
    .collect();

Если вы хотите просто пропустить ошибки, существует гораздо более элегантное решение с filter_map:

let seats: Vec<_> = data
    .lines()
    .filter_map(|l| l.ok())
    .map(replace)
    .filter_map(|l| u32::from_str_radix(&l, 2).ok())
    .collect();

Если вы хотите сохранить ошибки, поместите их в Box<dyn Error>, чтобы учесть различные типы:

use std::error::Error;
// later in the code
let seats: Result<Vec<_>, Box<dyn Error>> = data
    .lines()
    .map(|x| x.map_err(|e| Box::new(e) as _))
    .map(|l| l.map(replace))
    .map(|l| l.and_then(|s| u32::from_str_radix(&s, 2).map_err(|e| Box::new(e) as _)))
    .collect();

Если вам не нравится повторяющийся .map_err(|e| Box::new(e) as _), сделайте для него черту:

use std::error::Error;

trait BoxErr {
    type Boxed;
    fn box_err(self) -> Self::Boxed;
}

impl<T, E: Error + 'static> BoxErr for Result<T, E> {
    type Boxed = Result<T, Box<dyn Error>>;
    
    fn box_err(self) -> Self::Boxed {
        self.map_err(|x| Box::new(x) as Box<dyn Error>)
    }
}

// later in the code

let seats: Result<Vec<_>, Box<dyn Error>> = data
    .lines()
    .map(|x| x.box_err())
    .map(|l| l.map(replace))
    .map(|l| l.and_then(|s| u32::from_str_radix(&s, 2).box_err()))
    .collect();
person Aplet123    schedule 05.12.2020
comment
Спасибо за ответ. Есть ли способ сделать это так же элегантно, как вы, но при этом обнаруживать ошибки? - person Unapiedra; 05.12.2020
comment
@Unapiedra Я обновил свой ответ. - person Aplet123; 05.12.2020