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();
.read_to_string(&mut s)?;
fOk(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> {
.lines().next()?.chars().last()
text}
在 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
值退出。
以上,就是错误处理相关的内容了。