Rust错误处理和Result枚举类异常错误传递

Rust 有一套独特的处理异常情况的机制,它并不像其它语言中的 try 机制那样简单。

首先,程序中一般会出现两种错误:可恢复错误和不可恢复错误。

可恢复错误的典型案例是文件访问错误,如果访问一个文件失败,有可能是因为它正在被占用,是正常的,我们可以通过等待来解决。

但还有一种错误是由编程中无法解决的逻辑错误导致的,例如访问数组末尾以外的位置。

大多数编程语言不区分这两种错误,并用 Exception (异常)类来表示错误。在 Rust 中没有 Exception。

对于可恢复错误用 Result<T, E> 类来处理,对于不可恢复错误使用 panic! 宏来处理。

不可恢复错误

本章以前没有专门介绍 Rust 宏的语法,但已经使用过了 println! 宏,因为这些宏的使用较为简单,所以暂时不需要彻底掌握它,我们可以用同样的方法先学会使用 panic! 宏的使用方法。

实例

fn main() {panic!("error occured");println!("Hello, Rust");
}

运行结果:

thread 'main' panicked at 'error occured', src\main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

很显然,程序并不能如约运行到 println!("Hello, Rust") ,而是在 panic! 宏被调用时停止了运行。

不可恢复的错误一定会导致程序受到致命的打击而终止运行。

让我们注视错误输出的两行:

  • 第一行输出了 panic! 宏调用的位置以及其输出的错误信息。
  • 第二行是一句提示,翻译成中文就是"通过 `RUST_BACKTRACE=1` 环境变量运行以显示回溯"。接下来我们将介绍回溯(backtrace)。

紧接着刚才的例子,我们在 VSCode 中新建一个终端:

在新建的终端里设置环境变量(不同的终端方法不同,这里介绍两种主要的方法):

如果在 Windows 7 及以上的 Windows 系统版本中,默认使用的终端命令行是 Powershell,请使用以下命令:

$env:RUST_BACKTRACE=1 ; cargo run

如果你使用的是 Linux 或 macOS 等 UNIX 系统,一般情况下默认使用的是 bash 命令行,请使用以下命令:

RUST_BACKTRACE=1 cargo run

然后,你会看到以下文字:

thread 'main' panicked at 'error occured', src\main.rs:3:5
stack backtrace:...11: greeting::mainat .\src\main.rs:3...

回溯是不可恢复错误的另一种处理方式,它会展开运行的栈并输出所有的信息,然后程序依然会退出。上面的省略号省略了大量的输出信息,我们可以找到我们编写的 panic! 宏触发的错误。

可恢复的错误

此概念十分类似于 Java 编程语言中的异常。实际上在 C 语言中我们就常常将函数返回值设置成整数来表达函数遇到的错误,在 Rust 中通过 Result<T, E> 枚举类作返回值来进行异常表达:

enum Result<T, E> {Ok(T),Err(E),
}

在 Rust 标准库中可能产生异常的函数的返回值都是 Result 类型的。例如:当我们尝试打开一个文件时:

实例

use std::fs::File;fn main() {let f = File::open("hello.txt");match f {Ok(file) => {println!("File opened successfully.");},Err(err) => {println!("Failed to open the file.");}}
}

如果 hello.txt 文件不存在,会打印 "Failed to open the file."

当然,我们在枚举类章节讲到的 if let 语法可以简化 match 语法块:

实例

use std::fs::File;fn main() {let f = File::open("hello.txt");if let Ok(file) = f {println!("File opened successfully.");} else {println!("Failed to open the file.");}
}

如果想使一个可恢复错误按不可恢复错误处理,Result 类提供了两个办法:unwrap() 和 expect(message: &str) :

实例

use std::fs::File;fn main() {let f1 = File::open("hello.txt").unwrap();let f2 = File::open("hello.txt").expect("Failed to open.");
}

这段程序相当于在 Result 为 Err 时调用 panic! 宏。两者的区别在于 expect 能够向 panic! 宏发送一段指定的错误信息。

可恢复的错误的传递

之前所讲的是接收到错误的处理方式,但是如果我们自己编写一个函数在遇到错误时想传递出去怎么办呢?

实例

fn f(i: i32) -> Result<i32, bool> {if i >= 0 { Ok(i) }else { Err(false) }
}fn main() {let r = f(10000);if let Ok(v) = r {println!("Ok: f(-1) = {}", v);} else {println!("Err");}
}

运行结果:

Ok: f(-1) = 10000

这段程序中函数 f 是错误的根源,现在我们再写一个传递错误的函数 g :

实例

fn g(i: i32) -> Result<i32, bool> {let t = f(i);return match t {Ok(i) => Ok(i),Err(b) => Err(b)};
}

函数 g 传递了函数 f 可能出现的错误(这里的 g 只是一个简单的例子,实际上传递错误的函数一般还包含很多其它操作)。

这样写有些冗长,Rust 中可以在 Result 对象后添加 ? 操作符将同类的 Err 直接传递出去:

实例

fn f(i: i32) -> Result<i32, bool> {if i >= 0 { Ok(i) }else { Err(false) }
}fn g(i: i32) -> Result<i32, bool> {let t = f(i)?;Ok(t) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型
}fn main() {let r = g(10000);if let Ok(v) = r {println!("Ok: g(10000) = {}", v);} else {println!("Err");}
}

运行结果:

Ok: g(10000) = 10000

? 符的实际作用是将 Result 类非异常的值直接取出,如果有异常就将异常 Result 返回出去。所以,? 符仅用于返回值类型为 Result<T, E> 的函数,其中 E 类型必须和 ? 所处理的 Result 的 E 类型一致。


kind 方法

到此为止,Rust 似乎没有像 try 块一样可以令任何位置发生的同类异常都直接得到相同的解决的语法,但这样并不意味着 Rust 实现不了:我们完全可以把 try 块在独立的函数中实现,将所有的异常都传递出去解决。实际上这才是一个分化良好的程序应当遵循的编程方法:应该注重独立功能的完整性。

但是这样需要判断 Result 的 Err 类型,获取 Err 类型的函数是 kind()。

实例

use std::io;
use std::io::Read;
use std::fs::File;fn read_text_from_file(path: &str) -> Result<String, io::Error> {let mut f = File::open(path)?;let mut s = String::new();f.read_to_string(&mut s)?;Ok(s)
}fn main() {let str_file = read_text_from_file("hello.txt");match str_file {Ok(s) => println!("{}", s),Err(e) => {match e.kind() {io::ErrorKind::NotFound => {println!("No such file");},_ => {println!("Cannot read the file");}}}}
}

运行结果:

No such file

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/518106.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端请求的错误处理

全局考虑&#xff1a; 错误需要从三个方面考虑&#xff0c;范围一次减小&#xff1a; 网络层 > 协议层 > 应用层

鲁大师2月新机性能/流畅/AI/久用榜:魅族推出“传统智能手机”谢幕之作

结束了令人怀念的农历新年&#xff0c;无论是否还意犹未尽&#xff0c;整个行业&#xff0c;以及身处在这个行业的我们&#xff0c;都要重新投入到行业之中&#xff0c;开始期待和拥抱今年&#xff0c;又将会有哪些突破和成果迎接着我们。 手机厂商的AI大潮显然要比想象中来的更…

冬去春来天气阴晴不定 美食拿捏味蕾安稳换季

俗话说“春打六九头”&#xff0c;3月虽然已经入春&#xff0c;但是天气依然是凉飕飕的 &#xff0c;冬天春天的换季期&#xff0c;因为天气的变化&#xff0c;尤为痛苦。但是来到了换季期&#xff0c;天气也不总是那么稳定&#xff0c;随着气温的起伏&#xff0c;我们的食欲也…

php常见的45个漏洞及解决方案

[TOC]&#xff08;太多了&#xff0c;目录只列出最重要的几个&#xff0c;剩下的同学们自己翻&#xff09; PHP作为一种广泛应用的服务器端脚本语言&#xff0c;在历史上曾曝出过多种安全漏洞。以下是一些PHP漏洞的类别及其简要解释&#xff0c;以及如何解决这些问题&#xff1…

洗衣洗鞋店小程序对接水洗唛打印,一键预约,支付无忧

随着社会的进步和科技的发展&#xff0c;我们的生活幸福感与日俱增。为了让我们从琐碎中解脱出来&#xff0c;干洗店洗鞋店行业也日新月异。今天&#xff0c;我为大家推荐这款优秀的干洗店小程序系统&#xff0c;让您的洗衣洗鞋服务体验更上一层楼。 干洗店管理系统是一款专为洗…

利用MATLAB模拟点电荷的电场分布

一、实验目的&#xff1a; 1&#xff0e;熟悉点电荷的电场分布情况&#xff1b; 2&#xff0e;学会使用Matlab绘图&#xff1b; 二、实验内容&#xff1a; 1&#xff0e;根据库伦定律&#xff0c;利用Matlab强大的绘图功能画出单个点电荷的电场分布情况&#xff0c;包括电力…

填充每个节点的下一个右侧节点指针 II

题目链接 填充每个节点的下一个右侧节点指针 II 题目描述 注意点 初始状态下&#xff0c;所有 next 指针都被设置为NULL如果找不到下一个右侧节点&#xff0c;则将 next 指针设置为NULL 解答思路 首先想到的是层序遍历&#xff0c;将每一层的节点存储在队列中&#xff0c;…

鸿蒙文章专题-2021年鸿蒙相关的文章废弃

#原因 至于为什么说2021年我的鸿蒙专栏的文章废弃了&#xff0c;只是说没有了参考意义&#xff0c;是因为鸿蒙4.0以前的版本语言从以Java为主过渡为以ArkTS为主。以前的Java版本的工程已经无法再使用了&#xff0c;后续的开发都必须以ArkTS开发语言为主。 其中而且整个项目结构…

【硬件工程师面经整理21_其它】

文章目录 1 为何电源的滤波电路常常是大电容配合小电容滤波2 小信号敏感信号是什么&#xff0c;如何保护敏感信号3 555定时器4 CMOS不用输入管脚该怎么处理。5 为什么一个标准的倒相器中 P 管的宽长比要比 N 管的宽长比大&#xff1f;6 S11和反射系数和插损的关系7 眼图的功能8…

【Spring高级】第2讲:容器实现类

目录 BeanFactory实现BeanDefinition后置处理器单例bean创建后置处理器顺序总结 ApplicationContext实现ClassPathXmlApplicationContextFileSystemXmlApplicationContextAnnotationConfigApplicationContextAnnotationConfigServletWebServerApplicationContext BeanFactory实…

【论文阅读】单词级文本攻击TAAD2.2

TAAD2.2论文概览 0.前言1-101.Bridge the Gap Between CV and NLP! A Gradient-based Textual Adversarial Attack Frameworka. 背景b. 方法c. 结果d. 论文及代码 2.TextHacker: Learning based Hybrid Local Search Algorithm for Text Hard-label Adversarial Attacka. 背景b…

JavaScript极速入门(1)

初识JavaScript JavaScript是什么 JavaScript(简称JS),是一个脚本语言,解释型或者即时编译型语言.虽然它是作为开发Web页面的脚本语言而著名,但是也应用到了很多非浏览器的环境中. 看似这门语言叫JavaScript,其实在最初发明之初,这门语言的名字其实是在蹭Java的热度,实际上和…