当前位置: 首页 > news >正文

Rust编程学习 - 如何利用代数类型系统做错误处理的另外一大好处是可组合性(composability)

组合错误类型

利用代数类型系统做错误处理的另外一大好处是可组合性(composability)。比如 Result 类型有这样的一系列成员方法:

fn map < U,
F > (self, op: F) - >Result < U,
E > where F: FnOnce(T) - >U fn map_err < F,
0 > (self, op: 0) - >Result < T,
F > where O: FnOnce(E) - >F fn and < U > (self, res: Result < U, E > ) - >Result < U,
E > fn and_then < U,
F > (self, op: F) - >Result < U,
E > where F: FnOnce(T) - >Result < U,
E > fn or < F > (self, res: Result < T, F > ) - >Result < T,
F > fn or_else < F,
0 > (self, op: 0) - >Result < T,
F > where O: FnOnce(E) - >Result < T,
F > 

这些方法的签名稍微有点复杂,涉及许多泛型参数。它们之间的区别也就表现在方法签 名中。我们可以用下面的方式去掉语法干扰之后,来阅读函数签名,从而理解这些方法之间 的区别:
在这里插入图片描述
通过这个表格的对比,我们可以很容易看出它们之间的区别。比如map和and_ then 的主要区别是闭包参数:map的参数是做的T->U的转换,而and_then的参数是 T->Result 的转换。 Option 类型也有类似的对应的方法,读者可以自己建一个表格,对 比一下这些方法签名之间的区别。

下面用一个示例演示一下这些组合函数的用法:

use std: :env;
fn double_arg(mut argv: env: :Args) - >Result < i32,
String > {argv.nth(1).ok_or("Please     give     at     least     one     argument".to_owned()).and_then( | arg | arg.parse: :<i32 > ().map_err(lerr | err.to_string())).map( | n | 2 * n)
}
fn main() {match double_arg(env: :args()) {Ok(n) = >println ! ("{}", n),Err(err) = >println ! ("Error:{}", err),}
}

问号运算符

Result类型的组合调用功能很强大,但是它有一个缺点,就是经常会发生嵌套层次太 多的情况,不利于可读性。比如下面这个示例:

use std: :fs: :File;
use std: :io: :Read;
use std: :path: :Path;
fn file_double < P: AsRef < Path >> (file_path: P) - >Result < i32,
String > {File: :open(file_path).map_err( | err | err.to_string()).and_then( | mut file | {let mut contents = String: :new();file.read_to_string( & mut contents).map_err( | err | err.to_string()).map(I_I contents)}).and_then( | contents | {contents.trim()·parse: :<i32 > ().map_err( | err | err.to_string())}).map( | n | 2 * n)fn main() {match file_double("foobar") {Ok(n) = >println ! ("{}", n),Err(err) = >println ! ("Error:{}", err),}}

这说明我们还有继续改进的空间。为了方便用户,Rust 设计组在前面这套系统的基础上, 又加入了一个问号运算符,用来简化源代码。这个问号运算符完全是建立在前面这套错误处 理机制上的语法糖。

问号运算符意思是,如果结果是Err,则提前返回,否则继续执行。使用问号运算符,我们可以把 file_double 函数简化成这样:

fn file_double < P: AsRef < Path >> (file_path: P) - >Result < i32,
String > {let mut file = File: :open(file_path).map_err( | e | e.to_string()) ? ;let mut contents = String: :new();file.read_to_string( & mut contents).map_err( | err | err.to_string()) ? ;let n = contents.trim()·parse: :<i32 > ().map_err( | err | err.to_string()) ? ;Ok(2 * n)
}

这里依然有不少的map_err 调用,主要原因是返回类型限制成了Result<i32, String> 。 如果改一下返回类型,代码还能继续精简。
因为这段代码总共有两种错误:

  • 一种是io 错误,用std::io::Error 表示
  • 另外一种是字符串转整数错误,用std::num::ParseIntError表示。

我们要把这两种类型统一起 来,所以使用了一个自定义的enum 类型,这样map_err 方法调用就可以省略了。我们再补 充这两种错误类型到自定义错误类型之间的类型转换,问题就解决了。完整源码如下所示:

use std: :fs: :File;
use std: :io: :Read;
use std: :path: :Path;# [derive(Debug)] enum MyError {Io(std: :io: :Error),Parse(std: :num: :ParseIntError),
}
impl From < std: :io: :Error >
for MyError {fn from(error: std: :io: :Error) - >Self {MyError: :Io(error)}
}
impl From < std: :num: :ParseIntError >
for MyError {fn from(error: std: :num: :ParseIntError) - >Self {MyError: :Parse(error)}
}
fn file_double < P: AsRef < Path >> (file_path: P) - >Result < i32,
MyError > {let mut file = File: :open(file_path) ? ;let mut contents = String: :new();file.read_to_string( & mut contents) ? ;let n = contents.trim().parse: :<i32 > () ? ;Ok(2 * n)
}
fn main() {match file_double("foobar") {Ok(n) = >println ! ("{}", n),Err(err) = >println ! ("Error:{:?}", err),}
}

这样一来,这个file_double 函数就精简太多了。它只需管理正常逻辑,对于可能出 错的分支,直接一个问号操作符提前返回,错误处理和正常逻辑互不干扰,清晰易读。

下面继续讲解一下问号运算符背后做了什么事情。跟其他很多运算符一样,问号运算符 也对应着标准库中的一个trait std::ops::Try。它的定义如下:

trait Try {type Ok;type Error;fn into_result(self) - >Result < Self: :Ok,Self: :Error > ;fn from_error(v: Self: :Error) - >Self;fn from_ok(v: Self: :Ok) - >Self;
}match Try: :into_result(expr) {Ok(V) = >V,//here,the    'return    presumes    that    there    isErr(e) = >return Try: :from_error(From: :from(e)),
}

哪些类型支持这个问号表达式呢?标准库中已经为Option、Result两个类型impl了 这个trait:

impl < T > ops: :Try
for Option < T > {type Ok = T;type Error = NoneError;fn into_result(self) - >Result < T,NoneError > {self.ok_or(NoneError)}fn from_ok(V: T) - >Self {Some(V)}fn from_error(_: NoneError) - >Self {None}
}
impl < T,
E > ops: :Try
for Result < T,
E > {type Ok = T;type Error = E;fn into_result(self) - >Self {self}fn from_ok(v: T) - >Self {Ok(V)}fn from_error(v: E) - >Self {Err(V)}

把这些综合起来,我们就能理解对于Result类型,执行问号运算符做了什么了。其实 就是碰到Err的话,调用From trait 做个类型转换,然后中断当前逻辑提前返回。

http://www.dtcms.com/a/585125.html

相关文章:

  • LocalAI:一个免费开源的AI替代方案,让创意更自由!
  • 深入理解Ext2:Linux文件系统的基石与它的设计哲学
  • 泉州网站的建设html网页制作我的家乡
  • PHP 魔术常量
  • 【iOS】音频与视频播放
  • php通过身份证号码计算年龄
  • 基于PHP+Vue+小程序快递比价寄件系统
  • Next.js、NestJS、Nuxt.js 是 **Node.js 生态中针对不同场景的框架**
  • 牛客周赛 Round 114 Java题解
  • PostgreSQL 中数据库、用户、对象关系、表、连接及管理概述
  • 樟树市城乡规划建设局网站爱站攻略
  • Gitblit 迁移指南
  • Git分支管理核心:git fetch与git checkout创建分支完全指南
  • LRU 缓存的设计与实现
  • Linux -- 线程互斥
  • 2.2 Transformer 架构详解:从理论到实践
  • 《Docker+New Relic+Jenkins:开发全链路的工具赋能指南》
  • 2025最新修复的豪门足球风云-修复验证问题-修复注册问题实现地注册-架设教程【豪门足球本地验证】
  • 【Linux笔记】网络部分——数据链路层mac-arp
  • 深圳网站设计公司专业吗外国网站分享代码
  • VB.Net 常用函数
  • 成都哪家做网站wordpress 主题课堂
  • 智慧随访管理系统源码,基于Java+Spring Boot+Vue的随访系统源码,支持二次开发,支持患者信息管理、多类型随访、三级回访机制、问卷模板
  • MQL5 自学路线图:从入门到实战
  • 告别 mysqldump 痛点!用 mydumper 实现 MySQL 高效备份与恢复
  • 【Java 并发编程】线程创建 6 种方式:Thread/Runnable/Callable 核心类全解析
  • Lombok.jar bug
  • 隐藏在字符编码中的陷阱:深入剖析宽字节注入
  • STM32外设学习--TIM定时器--编码器接口(程序)
  • iis 网站关闭陕西省住房和城乡建设厅