0
点赞
收藏
分享

微信扫一扫

【Rust指南】错误的分类与传递|使用kind进行异常处理


【Rust指南】错误的分类与传递|使用kind进行异常处理_开发语言

文章目录

  • ​​  前言​​
  • ​​1、不可恢复错误​​
  • ​​1.1、panic! 宏的使用​​
  • ​​1.2、通过 Powershell命令行分析错误原因​​
  • ​​2、可恢复的错误​​
  • ​​2.1、Rustlt<T,E>枚举类的使用​​
  • ​​2.2、Result 类的unwrap() 和 expect(message: &str) 方法​​
  • ​​3、可恢复的错误的传递​​
  • ​​4、结合kind方法处理异常​​

  Rust 有一套独特的处理异常情况的机制,它并不像其它语言中的 try 机制那样简单。
在Rust 中的错误分为两大类:可恢复错误和不可恢复错误。大多数编程语言用 ​​​Exception​​​ (异常)类来表示错误。在 Rust 中没有 Exception。对于可恢复错误用 ​​Result<T, E>​​​ 类来处理,对于不可恢复错误使用 ​​panic!​​ 宏来处理。

1、不可恢复错误

  • 由编程中无法解决的逻辑错误导致的,例如访问数组末尾以外的位置

1.1、panic! 宏的使用

的使用较为简单,让我们来看一个具体例子:

fn main() {
panic!("Error occured");
println!("Hello, rust");
}

运行结果:


很显然,程序并不能如约运行到 ​​println!("Hello, rust")​​​ ,而是在 ​​panic!​​ 宏被调用时停止了运行,不可恢复的错误一定会导致程序受到致命的打击而终止运行。

1.2、通过 Powershell命令行分析错误原因

我们来分析一下终端命令行中的报错信息:

thread 'main' panicked at 'Error occured', src\main.rs:2:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

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

stack backtrace:
0: std::panicking::begin_panic_handler
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library\std\src\panicking.rs:584
1: core::panicking::panic_fmt
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library\core\src\panicking.rs:142
2: error_deal::main
at .\src\main.rs:2
3: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3\library\core\src\ops\function.rs:248

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

2、可恢复的错误

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

2.1、Rustlt枚举类的使用

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

enum Result<T, E> {
Ok(T),
Err(E),
}//T的类型不定,相当于C++中模板的写法

我们知道​​enum​​​常常与​​match​​​配合使用,当匹配到​​OK​​时就会执行相应代码。

在 Rust 标准库中可能产生异常的函数的返回值都是 ​​Result​​ 类型。

例如:当我们尝试打开一个文件时:

use std::fs::File;

fn main() {
let fp = File::open("hello_rust.txt");
match fp {
Ok(file) => {
println!("File opened successfully.");
},
Err(err) => {
println!("Failed to open the file.");
}
}
}//OK里的参数file是File类型,相当于填充了枚举里的T类型

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

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

use std::fs::File;

fn main() {
let fp = File::open("hello_rust.txt");
if let Ok(file) = fp {
println!("File opened successfully.");
} else {
println!("Failed to open the file.");
}
}

2.2、Result 类的unwrap() 和 expect(message: &str) 方法

  • 将一个可恢复错误按不可恢复错误处理

举个例子:

use std::fs::File;

fn main() {
let fp1 = File::open("hello_rust.txt").unwrap();
let fp2 = File::open("hello_rust.txt").expect("Failed to open.");
}

  • 这段程序相当于在 Result 为 ​​Err​​ 时调用 panic!宏
  • 两者的区别在于 ​​expect​​ 能够向 panic! 宏发送一段指定的错误信息
  • ​panic!​​宏是不可恢复错误,这样就完成了转变

3、可恢复的错误的传递

之前所讲的是接收到错误的处理方式,接下来讲讲怎么把错误信息传递出去

我们先来编写一个函数:

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

这里​​r​​​的结果是​​f​​​函数返回的​​ok(10000)​​​,经过​​if let​​​模式匹配后​​v​​的值为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 可能出现的错误,这样写有些冗长,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 类型一致。

4、结合kind方法处理异常

虽然前面提到Rust 异常不像其他语言这么简单,但这并不意味着 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_rust.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");
}
}
}
}
}//这里我没有创建hello_rust.txt文件,因此运行结果为:No such file

代码解释:

  • 使用​​read_text_from_file()​​函数将文件打开的结果传给了​​str_file​​变量
  • 这里并不存在​​hello_rust.txt​​​,因此​​File::open(path)?​​​不会打开文件,异常会存到​​f​​中
  • ​f.read_to_string(&mut s)?​​​并不能读出文件内容,​​ok(s)​​无内容
  • 通过分析,分支会执行​​Err(e)​​的代码块,使用​​e.kind()​​得到了错误类型并再次进行​​match​​分支
  • 如果是​​NotFound​​错误就会打印No such file
  • 其他情错误均提示Cannot read the file

​Rust 的错误处理到此分享结束,欢迎大家指点,如有不恰当的地方还请不吝提出,让我们在交流中进步!​


举报

相关推荐

0 条评论