【Rust自学】9.3. Result枚举与可恢复的错误 Pt.2:传播错误、?运算符与链式调用

news/2025/1/7 22:28:12/

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

9.3.1. 传播错误

当你编写的函数中包含了一些可能会执行失败的调用时,除了在函数里处理这个错误,还可以把错误返回给调用者,让它来决定如何进一步处理这个错误。

看个例子:

rust">use std::fs::File;  
use std::io::{self, Read};  fn read_username_from_file() -> Result<String, io::Error> {  let f = File::open("6657.txt");  let mut f = match f {  Ok(file) => file,  Err(e) => return Err(e),  };  let mut s = String::new();  match f.read_to_string(&mut s) {  Ok(_) => Ok(s),  Err(e) => Err(e),  }  
}fn main() {  let result = read_username_from_file();  
}

这个代码的意图是从文件中读取用户名:

  • 它的返回类型是Result枚举,它的两个参数TE对应String类型和io::Error类型,也就是说,当一切顺利的时候,会返回Result下的Ok变体,Ok里包裹着String类型的用户名,如果遇到了问题,这个函数就会返回Result下的Err变体,在这个变体里会包含io::Error的实例。

  • 下面看函数体,首先使用File::open函数尝试打开一个文件,把Result类型赋给f,然后对f进行match操作(这里把第二个的f设为可变是因为下文的read_to_string会使用&mut self),如果操作成功会返回file把值赋给f,如果操作失败就会return Err(e),这里的e就是具体发生的错误,而在函数体里面遇到return关键字就表示函数的执行到此为止,返回return后面的参数,也就是Err(e)这个变体,错误类型恰好是io::Error,所以说返回值符合result的类型参数。

  • 如果File::open能操作成功的话,接下来函数就创建了一个可变的String,叫s,然后调用read_to_string方法把文件里的内容读取到变量s里面。当然read_to_string方法也可能会失败,所以后面还跟了一个match表达式。

  • 这个match表达式它的结尾没有分号,它也是这个函数的最后一个表达式,所以说它就是这个函数的返回结果。这个match有两个分支,如果这个操作能成功的话,就返回Result的Ok变体,并且把String类型的变量s封装到里面;如果操作失败,就返回Err变体,把错误e包裹在里面返回,而read_to_string方法的返回值类型恰好也是io::Error,所以返回值符合result的类型参数。

9.3.2. ?运算符

在Rust里传播错误的设计是非常常见的,所以Rust还专门提供了?这个运算符来简化传播错误的过程。

使用?实现上文例子的同样效果:

rust">use std::fs::File;  
use std::io::{self, Read};  fn read_username_from_file() -> Result<String, io::Error> {  let mut f = File::open("6657.txt")?;  let mut s = String::new();  f.read_to_string(&mut s)?;  Ok(s)  
}  fn main() {  let result = read_username_from_file();  
}
  • 对于第一个?(第5行):File::open的返回类型是Result,然后加了?就是说如果File::open的返回值是Ok,那么包裹在Ok里的值就会作为表达式的结果返回赋给f,如果File::open的返回值是Err,那么就会终止函数的执行,把Err及里面包裹的错误信息作为整个函数的返回值返回(也就是return Err(e))。也就是说,第五行代码的效果等同于:
rust">let f = File::open("6657.txt");  
let mut f = match f {  Ok(file) => file,  Err(e) => return Err(e),  
};  
  • 对于第二个?(第7行):如果read_to_string操作成功,它就会继续往下执行,成功的返回值实际上在代码中没有用到,而如果执行失败的话,那么就会终止函数的执行,把Err及里面包裹的错误信息作为整个函数的返回值返回(也就是return Err(e))。

  • 如果前面都操作成功,那么就写表达式Ok(s)String类型的s包裹在Ok变体里返回。

总结一下:把?用于Result,如果是Ok,那么Ok中的值就是表达式的结果,然后程序继续执行;如果操作失败,也就是Err,那么Err就是整个函数的返回值,就像使用了return

9.3.3. ?from函数

Rust提供了from函数,它来自std::connvert::From这个trait,而它的作用是在错误之间进行转换,将一个错误类型转化为另外一个错误类型,而被?所接收的错误,会隐式地被from函数处理,from会看当前代码所在的函数的返回值的错误类型是什么,然后转换为什么。

就以刚才的代码为例,read_username_from_file函数的返回值是Result<String, io::Error>from函数就看得出来函数需要io::Error作为发生错误时的返回值,就会把不同的错误类型转化为io::Error,这里只是碰巧所有的函数体内的错误类型都是io::Error,就不需要转化这一步。

这个特点用于针对不同的错误原因,返回同一种错误类型的情况非常有用。但前提条件是涉及到的错误类型实现了转换为所返回的错误类型的from函数就可以。

9.3.4. 链式调用

其实之前的例子还可以继续优化,就是使用链式调用的形式。优化后的代码如下:

rust">use std::fs::File;  
use std::io::{self, Read};  fn read_username_from_file() -> Result<String, io::Error> {  let mut s = String::new();  File::open("6657.txt")?.read_to_string(&mut s)?;  Ok(s)  
}  fn main() {  let result = read_username_from_file();  
}

刚刚说过了,把?用于Result,如果是Ok,那么Ok中的值就是表达式的结果,然后程序继续执行。那就可以消除原代码中赋值的步骤,直接使用链式调用来执行。

9.3.5. ?只能用于返回Result类型的函数

看个例子:

rust">use std::fs::File;  
fn main() {  let result = File::open("6657.txt")?;  
}

输出:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)--> src/main.rs:3:40|
2 | fn main() {| --------- this function should return `Result` or `Option` to accept `?`
3 |     let result = File::open("6657.txt")?;|                                        ^ cannot use the `?` operator in a function that returns `()`|= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
help: consider adding return type|
2 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
3 |     let result = File::open("6657.txt")?;
4 +     Ok(())|

报错内容是?运算符只能用于返回值是Result或者Option这类实现了Try这个trait的类型,而main函数的返回类型是(),也就是单元类型,相当于什么也没返回。

但是,谁说main函数的返回类型一定是单元类型呢?只要把它的返回值改成Result类型不就完了吗?代码如下:

rust">use std::error::Error;  
use std::fs::File;  fn main() -> Result<(), Box<dyn Error>> {  let result = File::open("6657.txt")?;  Ok(())  
}
  • 把返回类型改为Result<(), Box<dyn Error>>,也就是说如果程序正常运行,会返回Ok这个变体,里面呢包裹着单元类型;如果没有正常运行,会返回Err这个变体,包裹着Box<dyn Error>(其中的Errorstd::error::Error),这是一个trait对象,在以后会讲,这里可以把它简单地理解为任何可能的错误类型。

  • 如果能成功读取,那么?就会把包裹在Ok里的文件数据返回赋给result,然后继续执行,Ok(())main函数里的最后一个表达式,它返回了Ok这个变体,同时把单元类型包裹着。

  • 如果不能成功读取,那么?就会把Err(e)作为main函数的返回值返回回去,并且函数执行到此结束。


http://www.ppmy.cn/news/1560635.html

相关文章

DC-2 靶场渗透

目录 环境搭建 开始渗透 扫存活 扫端口 扫服务 看一下80端口 看一下指纹信息 使用wpscan扫描用户名 再使用cewl生成字典 使用wpscan爆破密码 登陆 使用7744端口 查看shell rbash绕过 切换到jerry用户 添加环境变量 现在可以使用su命令了 提权 使用git提权 环…

CSS进阶和SASS

目录 一、CSS进阶 1.1、CSS变量 1.2、CSS属性值的计算过程 1.3、做杯咖啡 1.4、下划线动画 1.5、CSS中的混合模式(Blending) 二、SASS 2.1、Sass的颜色函数 2.2、Sass的扩展(extend)和占位符(%)、混合(Mixin) 2.3、Sass的数学函数 2.4、Sass的模块化开发 2.5、Sass…

Java实现UDP与TCP应用程序

三、Java实现UDP应用程序 3.1 InetAddress类 java.net.InteAddress类是用于描述IP地址和域名的一个Java类&#xff1b; 常用方法如下&#xff1a; public static InetAddress getByName(String host)&#xff1a;根据主机名获取InetAddress对象public String getHostName()…

【车载网络】BUSOFF状态简述和制造

BUSOFF Bus Off&#xff0c;即总线掉线&#xff0c;当前该节点脱离总线&#xff0c;不参与通信&#xff0c;可以理解为当前节点的Controller关闭&#xff0c;节点无法在此期间收/发报文。 注意&#xff0c;此期间ECU依然在正常运行&#xff0c;所有的任务依然被OS调度 TEC&am…

浏览器书签智能分类

浏览器书签智能分类工具 最近发现浏览器的书签越来越乱了&#xff0c;主要是因为自己太懒&#xff0c;其次之前建的分类太多又乱&#xff0c;重新手动整理确实比较烦。因此有了这个小项目。借助智谱AI的力量对书签进行重新分类。 项目简介 本工具用于自动整理浏览器书签&…

【ShuQiHere】算法的开枝散叶:从机器学习到深度学习的模型总结

【ShuQiHere】&#x1f680; 人工智能&#xff08;AI&#xff09;在过去十几年里经历了跨越式发展&#xff1a;从最初的计算机象征式推理到如今大行其道的深度学习&#xff0c;AI正逐步渗透到我们生活和工作的各个领域。然而&#xff0c;AI到底是如何一步步走到今天的&#xf…

AI数据标注师理论部分考试题库 - 500题

1.题目:职业道德是职场和行业内的特殊规范,这些规范是()在职业领域的具体实践。 A. 家庭道德 B. 社会道德 C. 仁义道德 D. 伦理道德 答案:B 解析:职业道德基于社会道德在职业场景的体现,与家庭、仁义、伦理道德概念不同,答案为 B。 2.题目:为了实现职业的健康发展,以…

python3中的字典推导式

一. 简介 前面简单学习了 python中的列表推导式&#xff0c;本文来简单学习一下 python中的字典推导式。 二. 字典推导式 python 中的字典推导式是 Python中创建字典的一种简洁方式。它允许你用一行代码来代替多行的 for循环和条件语句&#xff0c;从而快速地生成字典。 字典…