Rust 将错误分为两类:可恢复的(recoverable)、不可恢复的(unrecoverable)。
对于一个可恢复的错误,比如未找到文件,我们很能想告知用户,并让其重试。不可恢复的错误总是出现 bug 的征兆,比如数组越界,应当立刻停止程序。
Rust 没有异常,而使用 Result<T, E> 类型,以及
panic! 宏,在遇到不可恢复的错误时执行。
不可恢复错误
如果程序遇到了不可恢复的错误,可以调用 panic!
宏来退出:
fn main() {
panic!("crash and burn");
}默认情况下,将会有类似这样的报错:
thread 'main' panicked at 'crash and burn', src/bin/panic.rs:10:3
stack backtrace:
0: _rust_begin_unwind
1: core::panicking::panic_fmt
2: _09_01_panic::main
at ./src/bin/panic.rs:10:3
3: core::ops::function::FnOnce::call_once
at /private/tmp/rust-20220618-83111-1qsludx/rustc-1.61.0-src/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.在 panic 时,程序默认会开始展开(unwinding)并回溯调用栈并清理,这个过程有很多工作。另一种选择是直接终止(abort),直接让操作系统来清理,没有错误提示。可以这样修改配置来更改为 abort 行为:
[profile.release]
panic = 'abort'终止不需要调用栈元数据,所以二进制包会更小。(更多减少大小的技巧可以参见 min-sized-rust)
此外,可以设置 RUST_BACKTRACE 环境变量为 1
获取详细日志,设为 full 获取全日志。在
--release 情况下,也不会有 debug info。
可恢复错误
大部分错误没有严重到影响程序运行。可以使用 Result
类型处理错误:
enum Result<T, E> {
Ok(T),
Err(E),
}例如打开一个文件,就可能会失败:
use std::fs::File;
fn main() {
let f = File::open("Hello.txt");
}open 方法返回的正是 Result,具体类型是
Result<File, std::io::Error>。
此时我们可以通过 match 处理不同情况:
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}更进一步,我们可以为不同的错误采取不同的处理方法:
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)
}
},
};
}或者,使用闭包完成相同的逻辑(详细的介绍在以后进行):
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}传播错误
很多时候我们希望调用者处理该错误,所以我们可以将其向上传播(propagating)。
可以这样做:
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.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 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)
}? 操作服允许链式调用:
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}请注意,? 对象相当于提前返回。所以返回值类型必须为
Result 或其他实现了 FromResidual 的类型。
我们也可以将 ? 用于 Option:
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}在 main 函数中,也允许将 Result
作为返回值:
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}Box<dyn Error> 类型是一个 trait 对象(trait
object),将在后续详细讲解。
当返回 Ok 时,将会以 0 值退出。若返回
Err 则会以非 0 值退出。
以上,就是错误处理相关的内容了。
