Rust 将软件中的错误分为两个类型:可恢复错误和不可恢复错误。
对于可恢复错误,例如文件找不到,可以报告给调用者处理。而不可恢复错误,例如内存访问越界,则应该直接终止程序。
大部分语言并不区分这两者,而是统一使用 exception 机制。Rust 没有 exception,对于可恢复错误,Rust 使用 Result
作为返回值,对于不可恢复错误,用 panic!
宏终止程序。
不可恢复的错误使用 panic!
某些错误是调用者无法修复的,只能让程序终止。这时候我们使用 panic!
宏:
fn main() {
panic!("crash and burn");
}
错误信息是:
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Using a panic!
Backtrace
我们常常遇到的情况是被调用者 panic,而非手动 panic。如:
fn main() {
let v = vec![1,2,3];
v[99];
}
结果是:
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
提示我们可以通过 RUST_BACKTRACE
环境变量来跟踪到底什么触发了错误:
RUST_BACKTRACE=1 cargo run
可恢复错误使用 Result
对于使用方法错误(如用户输入的参数错误),使用 panic!
不是很合适。此外,大部分错误没有严重到需要让程序停止运行。这种情况下可以使用 Result
类型作为返回值:
enum Result {
Ok(T),
Err(E),
}
例如,如果我们要打开一个文件,需要处理文件不存在的情况:
use std::fs::File;
fn main() {
let f = File::open("file.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error)
};
}
这时候如果路径下不存在 file.txt
这个文件,则会报错:
thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
语法糖:unwrap
和 expect
上面代码的 pattern 非常常见,因此 Result
类型定义了一些方法方法来减少开发者的劳动。
-
unwrap
:如果Ok
,返回Ok
中包含的值,如果Err
,调用panic!
。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
-
expect
:类似于unwrap
,但是允许我们自定义提示信息。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
传递错误
有时我们希望将错误交给调用者处理,此时,我们就需要传递(propagating)这个错误。
use std::fs;
use std::io;
use std::io::Read;
use std::process;
fn read_username_from_file(filename: &str) -> Result {
let f = fs::File::open(filename);
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
let result = match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
};
return result;
}
fn main() {
let filename = "a.txt";
match read_username_from_file(filename) {
Ok(s) => println!("read from file {} succeeded, {}", filename, s),
Err(e) => {
println!("read from file {} failed: {}", filename, e);
process::exit(1);
}
};
}
read_username_from_file
函数内部并不处理错误,而是通过返回一个 Result
类型来让调用者处理。在这里,错误类型是 io::Error
,可能由 File::open
或 read_to_string
方法引发。当返回错误时,调用者可以根据需求编写处理逻辑,例如使用默认用户名,或是像上面代码一样报错退出。
传递错误的语法糖:?
操作符
传递错误的逻辑非常常见,因此 Rust 提供了 ?
来简化代码。
在 Result
值后加 ?
的效果是:
-
Ok
:返回Ok
中的值,并继续执行 -
Err
:跳出整个函数,并返回错误
因此,上面的函数可以简化为:
fn read_username_from_file(filename: &str) -> Result {
let mut f = fs::File::open(filename)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
return Ok(s);
}
?
操作符已经省略了许多重复代码,它还支持链式调用,更加简洁:
fn read_username_from_file(filename: &str) -> Result {
let mut s = String::new();
fs::File::open(filename)?.read_to_string(&mut s)?;
return Ok(s);
}
不指定错误类型
如果我们不关心错误类型,我们可以将io::Error
替换为 Box
。它可以是任何实现了 Error
trait 的类型,约等于所有的错误类型。
fn read_username_from_file(filename: &str) -> Result> {
let mut s = String::new();
fs::File::open(filename)?.read_to_string(&mut s)?;
return Ok(s);
}