Rust 异常处理
Panic
Panic Shot! —— Onikata Kayoko
rust 提供了一个panic!
的宏 —— 当程序执行到这个宏的时候,会打印出错信息后,展开报错点往前的函数调用堆栈,最后退出程序 —— 其实是退出该线程,所以如果在 main 中遇到 panic,就会直接退出 main 线程导致程序意外终止。所以还是要小心使用 panic。
也就是说,使用 panic 处理的错误应当是直接影响到程序正常运行的错误,即不可恢复错误。
Result 和 Option
既然与 Panic 同一层级,说明这个类型可以用于处理可恢复错误
基础使用
首先说下 Result,它的本质是枚举型
enum Result<T, E> {
Ok(T),
Err(E),
}
如果是程序正确执行,就返回一个Ok(T)
的泛型数据;如果程序出现异常,就返回Err(E)
的泛型数据
其中T
可以看作正常程序的返回值;E
则存放了错误信息; 接下来回到小学还没学过负数的时候,我们眼中只知道使用大的数减去小的数,否则就觉得这是个错误的算式,就可以得到如下代码 ——
fn sub_without_negative(x: i32, y: i32) -> Result<i32, String> {
if x < y {
Err(String::from("x is lower than y"))
} else {
Ok(x - y)
}
}
然后在使用的时候,就可以使用模式匹配去处理对应的错误,比如
let res = match sub_without_negative(x, y) {
Ok(r) => r,
Err(msg) => {
panic!("Encounter an error —— {:?}", msg)
}
};
那么为什么这里使用panic!
宏呢?因为小时候遇到这种情况的时候大脑宕机了,开始怀疑人生,于是直接退出当前线程~~(不去多想)~~ 当然,由于我们使用 match 捕捉到了 Err 的情况,就可以对此进行一些处理,使得程序至少可以正常运行下去,而不是使用panic!
宏来处理。
执意使用 panic
对的兄弟,对的。当然可以用一些化简的写法来在匹配到 Err 后使用 panic。
unwarp()
—— 没有额外的提示信息,直接给 panic 出线程except()
—— 可以带上一点错误打印信息
错误传播
基本上生产实践中不可能只有一层的函数调用,大概率都是一层套一层的,非常麻烦。于是我们需要一种把错误从调用链的下层往上游传播的手段。
首先自然想到的,就是把调用层遇到的 Error 重新包一个Err(E)
传给调用链上层—— (这里使用的《Rust 圣经的例子》) 因为懒,非常的好
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
// 打开文件,f是`Result<文件句柄,io::Error>`
let f = File::open("hello.txt");
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};
// 创建动态字符串s
let mut s = String::new();
// 从f文件句柄读取数据并写入s中
match f.read_to_string(&mut s) {
// 读取成功,返回Ok封装的字符串
Ok(_) => Ok(s),
// 将错误向上传播
Err(e) => Err(e),
}
}
但是这样写非常的繁琐,于是简单的使用一个?
来简化这些代码 (例子代码还是来自《Rust 圣经》非常好的一本书!
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
?
的本质就是一个宏,把繁琐的 match 打包了一下而已。
特性:?
支持链式调用
foo_outer()?.foo_inner()?.foo_inner_inner()?; // 开个玩笑
File::open(DATA_PATH)?.read_to_string(&mut file_data)?
Option
最后谈到 option,它有点像一个可能为空的值(比如在C#中使用?表示一下改属性可能为空,有点类似) 不过谈及根本,其实更像是 Result 的一个特例
pub enum Option<T> {
Some(T),
None
}
也是一个枚举型,其中如果正常执行,就返回Some(T)
,如果出现异常,就返回None
有点像我们定义了一个Result<Some(T), ()>
遇到错误简单的返回一个空,想到的运用场景 ——
- 错误比较简单,一眼顶真
- 不太可能出现错误
- 简单的搭建一下原型,因为写 Result 太复杂
另外的,Option
也支持使用?
进行链式调用
写在最后
进行一个非常非常简略的小总结——
- 恶劣到影响程序运行的错误,可以使用
panic!
- 基本上使用
Result
偏多 - 简单情况可以使用
Option
当然,实际上还会更复杂一点(未来可期)
- 错误类型转化
- 嵌套错误的匹配处理;组合器 ——
or, then
之类的 - 自定义错误
- 等
特别的,main
函数也可以带有异常处理的返回值,比如
fn main() -> Result<(), Err(E)>
其中 Err(E)
就自行填入需要的类型罢!