rust 如何拼合嵌套的结果?

vnjpjtjt  于 2023-01-02  发布在  其他
关注(0)|答案(2)|浏览(142)

我正在使用一个第三方库,它提供了基于树的数据结构,我必须“按原样”使用。API返回Result<T, Error>。我必须进行一些顺序调用,并将错误转换为应用程序的内部错误。

use std::error::Error;
use std::fmt;

pub struct Tree {
    branches: Vec<Tree>,
}

impl Tree {
    pub fn new(branches: Vec<Tree>) -> Self {
        Tree { branches }
    }

    pub fn get_branch(&self, id: usize) -> Result<&Tree, TreeError> {
        self.branches.get(id).ok_or(TreeError {
            description: "not found".to_string(),
        })
    }
}

#[derive(Debug)]
pub struct TreeError {
    description: String,
}

impl Error for TreeError {
    fn description(&self) -> &str {
        self.description.as_str()
    }
}

impl fmt::Display for TreeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.description.fmt(f)
    }
}

#[derive(Debug)]
pub struct MyAwesomeError {
    description: String,
}

impl MyAwesomeError {
    pub fn from<T: fmt::Debug>(t: T) -> Self {
        MyAwesomeError {
            description: format!("{:?}", t),
        }
    }
}

impl Error for MyAwesomeError {
    fn description(&self) -> &str {
        &self.description
    }
}

impl fmt::Display for MyAwesomeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.description.fmt(f)
    }
}

如果我写这个代码:

pub fn take_first_three_times(tree: &Tree) -> Result<&Tree, MyAwesomeError> {
    let result = tree
        .get_branch(0)
        .map(|r| r.get_branch(0))
        .map(|r| r.map(|r| r.get_branch(0)));
    //    ...
}

result的类型将是Result<Result<Result<Tree, TreeError>, TreeError>, TreeError>。我不希望通过级联match来处理错误。
我可以编写一个内部函数来调整API的接口,并在基函数级别处理错误:

fn take_first_three_times_internal(tree: &Tree) -> Result<&Tree, TreeError> {
    tree.get_branch(0)?.get_branch(0)?.get_branch(0)
}

pub fn take_first_three_times(tree: &Tree) -> Result<&Tree, MyAwesomeError> {
    take_first_three_times_internal(tree).map_err(MyAwesomeError::from)
}

如果没有额外的功能,我如何实现这一点?

jw5wzhpr

jw5wzhpr1#

这是一个问题的例子,当你在函数式编程中使用像Option这样的 Package 器时,在函数式编程中有一些叫做'pure'的函数,它们不改变一些状态(全局变量,输出参数)只依赖于输入参数,并且只将它们的结果作为返回值返回,没有任何副作用。它使程序更可预测和安全,但也带来了一些不便。
假设我们有let x = Some(2)和某个函数f(x: i32) -> Option<f32>,当你用mapf应用到x时,你会得到嵌套的Option<Option<f32>>,这和你得到的问题是一样的。
但在函数式编程的世界里(Rust受到了他们想法的很多启发,并支持很多典型的“函数式”特性),他们提出了解决方案:单子。
我们可以向map显示一个类似(A<T>, FnOnce(T)->U) -> A<U>的签名,其中A类似于 Package 器类型,例如OptionResult。在FP中,这样的类型被称为函子。但是它有一个高级版本,称为monad。除了map函数之外,它的接口中还有一个更类似的函数。传统上称为bind,签名类似(A<T>, FnOnce(T) -> A<U>) -> A<U>。更多详细信息there
实际上,Rust的OptionResult不仅是一个函子,也是一个单子,在我们的例子中,bind被实现为and_then方法,例如,你可以在我们的例子中这样使用它:x.and_then(f),并得到简单的Option<f32>作为结果。所以,代替.map链,你可以得到.and_then链,它的行为非常相似,但不会有嵌套的结果。

41zrol4v

41zrol4v2#

结合and_then并使用?运算符,我的解决方案如下:

pub fn take_first_three_times(tree: &Tree) -> Result<&Tree, MyAwesomeError> {
    tree.get_branch(0).and_then(|first| {
        first.get_branch(0)?.get_branch(0)
    }).map_err(MyAwesomeError::from)
}

实际上,这只是将内部函数替换为FnOnce闭包

相关问题