简单的错误处理
Rust 有一套独特的处理异常情况的机制,它把错误分为两种:可恢复错误和不可恢复错误。
不可恢复错误指的是在编程中的逻辑错误导致的。
可恢复错误指的是程序或者命令执行失败,通过等待、重载等方式可以解决,意料之内的行为。
在 Rust 中有多种处理错误的方式,它们多少有些区别,使用场景也不尽相同。
- panic 主要用于测试,以及处理不可恢复的错误。在原型开发中这很有用,比如用来测试还没有实现的函数。
- Option 类型是为了值是可选的、或者缺少值并不是错误的情况准备的。比如说寻找父目录时,/ 和 C: 这样的目录就没有父目录,这应当并不是一个错误。
- 当错误有可能发生,且应当由调用者处理时,使用 Result。
可恢复错误
Option
在标准库中有个叫做 Option<T> 的枚举类型,它表现为以下两个 “option”(选项)中的一个:
- Some(T):找到一个属于 T 类型的元素
- None:找不到相应元素
当你有一个可能存在,也可能不存在的值时,你就用Option
。当一个值存在的时候就是Some(value)
,不存在的时候就是None
。代码示例:
rust
fn take_fifth(value: Vec<i32>) -> Option<i32> {
if value.len() < 5 { // .len() gives the length of the vec.It must be at least 5.
None
} else {
Some(value[4])
}
}
fn main() {
let new_vec = vec![1, 2];
let bigger_vec = vec![1, 2, 3, 4, 5];
println!("{:?}, {:?}", take_fifth(new_vec), take_fifth(bigger_vec));
}
Option 可以和 match 结合使用显示的进行处理:
rust
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
或者使用 unwrap 隐式地处理。隐式处理要么返回 Some 内部的元素,要么就 panic。比如在下面打开文件的示例中,可以使用 unwrap 和 expect 简化这个过程。它的作用就是如果返回成功就将 Ok(T) 中的值取出,如果失败直接进行 panic 。
rust
use std::fs::File;
fn main() {
let _f1 = File::open("hello_1.txt").unwrap();
let _f2 = File::open("hello_2.txt").expect("Failed to open hello_2.txt");
}
使用 match 语句来解开 Option,但使用 ?
运算符通常会更容易:
rust
fn next_birthday(current_age: Option<u8>) -> Option<String> {
// 如果 `current_age` 是 `None`,这将返回 `None`。
// 如果 `current_age` 是 `Some`,内部的 `u8` 将赋值给 `next_age`。
let next_age: u8 = current_age?;
Some(format!("Next year I will be {}", next_age))
}
Result
大部分错误并没有严重到需要程序完全停止执行。有时,一个程序可能会遇到文件打开失败、连接服务器失败等错误,这时候直接程序崩溃不是正确的做法。
更通用的做法是是利用 Result
枚举类作返回值来进行异常表达,然后对一场进行处理。它定义有如下两个成员,Ok
和 Err
,T
表示成功返回的成员的类型,E
表示失败时返回的错误的类型:
rust
enum Result<T, E> {
Ok(T),
Err(E),
}
针对返回的Result
我们可以用模式匹配来对返回的成功或者失败进行处理,例如当打开一个文件时,如果打开成功那么将Ok(file)
中的file
赋值到t
中。如果失败,将返回一个error
赋值到t
中。后续的处理中,我们可以根据返回值进行操作。
rust
use std::fs::File;
fn main() {
let t = File::open("hello.txt");
let _f = match t {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
此外,在实际的应用中,我们可以匹配不同的错误,然后对应不同的处理方式:
rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}
上面代码在匹配出 error
后,又对 error
进行了详细的匹配解析,最终结果:
- 如果是文件不存在错误
ErrorKind::NotFound
,就创建文件,这里创建文件File::create
也是返回Result
,因此继续用match
对其结果进行处理:创建成功,将新的文件句柄赋值给f
,如果失败,则panic
- 剩下的错误,一律
panic
不可恢复错误
panic
最简单的错误处理机制是panic
。在以下的代码中它打印一条错误消息,开始展开堆栈,并且通常退出程序。示例:
rust
fn main() {
panic!("程序崩溃");
}
在程序中发生panic错误的话可以通过设置 RUST_BACKTRACE
环境变量,然后运行程序来获得一个 backtrace
。backtrace
是一个执行到目前报错位置所有被调用的函数的列表。
例如在命令行中运行命令:RUST_BACKTRACE=1 cargo run
assertion
断言表现为布尔表达式,在一些场景下,使用断言判断当前的计算结果和预期是否一致。Rust 提供了一些非常好用的断言:
assert!
,assert_eq!
,assert_ne!
, 它们会在所有模式下运行
rust
fn main() {
let a = 3;
let b = 1 + 2;
// 判断两者是否相等,如果不相等,会直接panic!
assert_eq!(a, b);
// 判断两者是否不相等,如果相等则panic!
assert_ne!(a, b);
// 判断传入的布尔表达式是否为 true,如果是false则panic!
let x = true;
assert!(x);
}
debug_assert!
,debug_assert_eq!
,debug_assert_ne!
, 它们只会在Debug
模式下运行,如果在release
模式下将不会执行。
rust
fn main() {
let a = 3;
let b = 1 + 3;
// 判断两者是否相等
debug_assert_eq!(a, b);
// 判断两者是否不相等,如果相等则panic!
debug_assert_ne!(a, b);
// 判断传入的布尔表达式是否为 true,如果是false则panic!
let x = true;
debug_assert!(x);
}