error-handling - 如何展平嵌套的结果?
问题描述
我正在使用第三方库,它提供了我必须“按原样”使用的基于树的数据结构。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>
。当您将map
其应用于f
时x
,您将得到嵌套Option<Option<f32>>
,这与您遇到的问题相同。
但是在函数式编程的世界中(Rust 受到了他们的很多启发,并且支持了很多典型的“函数式”特性),他们提出了解决方案:monads。
我们可以显示map
一个签名,比如(A<T>, FnOnce(T)->U) -> A<U>
where A
is something like wrapper type,例如Option
or Result
。在 FP 中,这种类型称为函子。但它有一个高级版本,称为 monad。除了map
函数之外,它的接口中还有一个类似的函数,传统上bind
用类似的签名来调用(A<T>, FnOnce(T) -> A<U>) -> A<U>
。那里有更多细节。
事实上,Rust 的Option
andResult
不仅是一个函子,还是一个单子。在我们的例子中,它bind
被实现为and_then
方法。例如,您可以在我们的示例中像这样使用它:x.and_then(f)
,Option<f32>
结果变得简单。.map
因此,您可以使用具有非常相似作用的链来代替.and_then
链,但不会有嵌套结果。
推荐阅读
- javascript - 当我使用 useChange 从数字键盘设置值时,挂钩调用无效
- python - 如何使用 if-else 语句在多个列表中插入相等数量的值?
- java - Scala枚举映射实现
- mysql - 如何从数据库连接表中查询并使用日期或范围之间?
- javascript - Chart.js update() 方法最终只工作一次,而不是每次迭代都更新
- amazon-web-services - 使用 VPN 网关在本地将 AWS EC2 连接到 Google Cloud SQL 实例
- shiny-server - 希望在 R 闪亮的服务器中运行 R GUI
- typescript - 从泛型函数返回空数组
- vim - 方便的单键重新映射,用于在 Vim 中向上翻页和向下翻页
- html - Cordova 内容安全策略 frame-src 错误