在Rust中对Results的迭代器调用map函数

8

我希望以“函数式编程”风格编写一些代码。

但是,我从一个结果的迭代器开始,我只想将函数应用于Ok项。此外,我想在第一个错误停止迭代(不过,我可以接受不同的行为)。

目前,我正在使用嵌套的map()模式:<iter>.map(|l| l.map(replace))。我觉得这非常丑陋。

使用夜间版“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")
}

更多参考资料:

1个回答

9

既然你无论如何都要丢弃错误类型,那么你可以完全避免使用eyre,并使用.okResult转换为Option,然后仅使用Optionand_then来避免每次展平:

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 _),那么可以为其创建一个trait:

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

谢谢你的回答。有没有一种优雅的方法可以检测错误? - Unapiedra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接