【问题标题】:How can I flatten nested Results?如何展平嵌套的结果?
【发布时间】:2020-03-20 12:54:36
【问题描述】:

我正在使用第三方库,它提供了我必须“按原样”使用的基于树的数据结构。 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&lt;Result&lt;Result&lt;Tree, TreeError&gt;, TreeError&gt;, TreeError&gt;。我不想通过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)
}

如何在没有附加功能的情况下实现这一点?

【问题讨论】:

标签: error-handling rust flatten


【解决方案1】:

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

假设我们有let x = Some(2) 和一些函数f(x: i32) -&gt; Option&lt;f32&gt;。当您使用map 将该f 应用于x 时,您将嵌套Option&lt;Option&lt;f32&gt;&gt;,这与您遇到的问题相同。

但是在函数式编程的世界里(Rust 的想法受到了很多启发,并且支持许多典型的“函数式”特性),他们提出了解决方案:monad。

我们可以向map 显示类似(A&lt;T&gt;, FnOnce(T)-&gt;U) -&gt; A&lt;U&gt; 的签名,其中A 类似于包装器类型,例如Option 或Result。在 FP 中,这种类型称为函子。但它有一个高级版本,称为 monad。除了map 函数之外,它的接口中还有一个类似的函数,传统上称为bind,其签名类似于(A&lt;T&gt;, FnOnce(T) -&gt; A&lt;U&gt;) -&gt; A&lt;U&gt;。更多详情there.

事实上,Rust 的Option 和Result 不仅是一个函子,也是一个单子。在我们的例子中,bind 实现为 and_then 方法。例如,您可以在我们的示例中像这样使用它:x.and_then(f),结果得到简单的Option&lt;f32&gt;。因此,您可以使用.and_then 链来代替.map 链,它的作用非常相似,但不会有嵌套结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-24
    • 2014-08-29
    • 1970-01-01
    • 1970-01-01
    • 2012-11-21
    • 1970-01-01
    • 2019-01-21
    • 2019-10-31
    相关资源
    最近更新 更多