我正在与一个第三方库合作,该库提供基于树的数据 struct ,我必须按原样使用.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)
}

如果没有附加功能,我如何实现这一点?

推荐答案

这是一个问题的例子,当你在函数式编程中使用各种包装器时,比如Option.在函数式编程中,有一些被称为"纯"函数,它们不改变某些状态(全局变量、输出参数),只依赖于输入参数,只将其结果作为返回值返回,没有任何副作用.它使程序更加可预测和安全,但也带来了一些不便.

假设我们有let x = Some(2)个和一些函数f(x: i32) -> Option<f32>.当你使用mapf应用到x时,你会得到嵌套的Option<Option<f32>>,这与你得到的问题相同.

但在函数式编程领域(Rust的灵感来源于他们的很多 idea ,并支持许多典型的"函数"特性),他们提出了解决方案:monads.

我们可以向map显示(A<T>, FnOnce(T)->U) -> A<U>这样的签名,其中A类似于包装类型,比如OptionResult.在FP中,这种类型称为函子.但它有一个高级版本,叫做单子.除了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链,它们的行为非常相似,但不会有嵌套的结果.

Rust相关问答推荐

为什么是!为Rust中的RwLockReadGuard和RwLockWriteGuard实现的发送特征?

如何在Rust中获得不可辩驳的'if let'模式警告Mutex锁定?""

为什么`Vec i64`的和不知道是`Option i64`?

在0..1之间将U64转换为F64

应为关联类型,找到类型参数

try 实现线程安全的缓存

使用占位符获取用户输入

为什么某些类型参数仅在特征边界中使用的代码在没有 PhantomData 的情况下进行编译?

如何在 Rust 中将函数项变成函数指针

为什么 Rust 字符串没有短字符串优化 (SSO)?

如何为已实现其他相关 std trait 的每个类型实现一个 std Trait

如何在 Rust 中按 char 对字符串向量进行排序?

为什么 &i32 可以与 Rust 中的 &&i32 进行比较?

预期的整数,找到 `&{integer}`

只有一个字符被读入作为词法分析器的输入

如何在 Rust 中返回通用 struct

我如何将 google_gmail1::Gmail> 传递给线程生成?

带有库+多个二进制文件的Cargo 项目,二进制文件由多个文件组成?

有没有办法在 Rust 中对 BigInt 进行正确的位移?

为什么在使用 self 时会消耗 struct 而在解构时不会?