Result<T, E>
为应对 panic,展开或中止(abort)调用栈
panic = 'abort'
使用 panic! 产生的回溯信息
RUST_BACKTRACE
可得到回溯信息
set RUST_BACKTRACE=1 && cargo run
RUST_BACKTRACE=1 cargo run
--release
)Result 枚举类型的定义:
enum Result<T, E> {
Ok(T),
Err(E),
}
T:操作成功情况下 Ok 变体里返回的数据的类型 E:操作失败情况下 Err 变体里返回的错误的类型
处理 Result 的一种方式:match 表达式。和 Option 枚举一样,Result 及其变体也是由 prelude 带入作用域,例子如下:
fn test02() {
let file = File::open("foo.txt");
let f = match file {
Ok(file) => file,
Err(error) => {
panic!("Open File Error: {:?}", error);
}
};
}
我们鼠标悬停在 file 变量上,可以看到它的类型是:std::result::Result<std::fs::File, std::io::Error>
,说明 open 函数返回的是一个 Result 枚举,且其第一个参数就是该文件,第二个参数是 io 下的 Error 类型,包含了错误的具体信息。
最终输出结果如下:
➜ ~/Code/rust/panic git:(master) ✗ cargo run
Compiling panic v0.1.0 (/home/cherry/Code/rust/panic)
warning: unused variable: `f`
--> src/main.rs:17:9
|
17 | let f = match file {
| ^ help: if this is intentional, prefix it with an underscore: `_f`
warning: `panic` (bin "panic") generated 2 warnings
Finished dev [unoptimized + debuginfo] target(s) in 0.46s
Running `target/debug/panic`
thread 'main' panicked at 'Open File Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:20:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
匹配不同的错误
fn test03() {
let f = match File::open("foo") {
Ok(file) => file,
Err(error) => match error.kind(){
ErrorKind::NotFound => match File::create("foo") {
Ok(file) => file,
Err(error) => panic!("Creating File Error: {:?}", error)
}
other_error => panic!("Open File Error: {:?}", other_error)
}
};
}
在 Err 中也会有很多种类型的错误,我们尝试匹配不同的错误类型,例如 NotFound
。
这里使用了很多 match,尽管很有用,但是比较原始。我们可以使用 闭包(closure) ,Result 有很多方法,他们使用闭包作为参数,使用 match 进行实现,使用这些方法会使得代码更简洁
fn test04() {
let f = File::open("foo.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("foo").unwrap_or_else(|error| {
panic!("Creating File Error: {:?}", error);
})
} else {
panic!("Open File Error: {:?}", error);
}
});
}
具体内容到后面再讲。
unwrap
unwrap 是 match 表达式的一个快捷方法,如果 Result 结果是 Ok 则返回 Ok 里面的值,如果 Result 结果是 Err 则调用 panic!宏。以刚刚这段代码举例:
fn test02() {
let file = File::open("foo.txt");
let f = match file {
Ok(file) => file,
Err(error) => {
panic!("Open File Error: {:?}", error);
}
};
}
unwrap 的作用类似于上面这段代码,当成功打开文件时,unwrap 就会返回 Ok 里面的值,否则就会调用 Err 代码块的代码,上面那段代码用 unwrap 就可以这样写:let f = File::open("foo.txt").unwrap();
但是发生恐慌的信息不可以自定义,这也是 unwrap 的一个缺点,而 Rust 提供了另一个方法:expect。
expect
和 unwrap 类似,但是可以指定错误信息:let f = File::open("foo").expect("Open File Error!!!");
,这样得到的报错信息如下:
➜ ~/Code/rust/panic git:(master) ✗ cargo run
Compiling panic v0.1.0 (/home/cherry/Code/rust/panic)
warning: unused variable: `f`
--> src/main.rs:26:9
|
26 | let f = File::open("foo").expect("Open File Error!!!");
| ^ help: if this is intentional, prefix it with an underscore: `_f`
warning: `panic` (bin "panic") generated 1 warnings
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
Running `target/debug/panic`
thread 'main' panicked at 'Open File Error!!!: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:26:31
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
当写的函数中包含可能会执行失败的调用的时候,除了可以在函数中处理这个错误,还可以将错误返回给函数的调用者,让他们来决定如何进一步处理这个错误,这就叫做 传播错误
fn read_text_from_file() -> Result<String, Error> {
let f = File::open("foo");
let mut f = match f {
Ok(file) => file,
Err(error) => return Err(error)
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(error) => Err(error)
}
}
fn main() {
let result = read_text_from_file();
println("{:?}", result);
}
将 Result 设置成函数返回值,这样就将错误传递给了调用者,若文件 foo 存在的话,最终便可以输出文件中的内容。
Rust 中还提供了 ?
运算符,用其来简化传播错误的操作。
如果 Result 是 Ok:Ok 中的值就是表达式的结果,然后继续执行程序; 如果 Result 是 Err:Err 就是 整个函数 的返回值,就像使用了 return。例子如下:
fn read_text_from_file_easy() -> Result<String, Error> {
let mut f = File::open("foo")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
上面这段简化后的代码的含义就是,若 ?
前 Result 类型的值是 Ok,那么 Ok 里的值就会作为表达式的返回值进行返回,若类型是 Err,那么 Err 就当做整个函数的返回值进行返回。而 f.read_to_string(&mut s)?;
中,若 Result 类型是 Ok,实际上里面值为空,没有用到,因此当表达式返回 Ok 后,返回一个 Ok(s) 作为函数的返回值,若类型为 Err,则将其作为函数返回值进行返回。
上面这个例子还可以继续进行优化,使用链式调用:
fn read_text_from_file_easist() -> Result<String, Error> {
let mut s = String::new();
File::open("foo")?.read_to_string(&mut s)?;
Ok(s)
}
值得注意的是,要使用 ? 运算符,必须保证函数返回类型为 Result,倘若我们尝试一下函数返回类型不是 Result,将会得到这样一条报错信息:error[E0277]: the '?' operator can only be used in a function that returns 'Result' or 'Option' (or another type that implements 'FromResidual')
因此,? 运算符只能用于返回类型为 Result 或 Option 的函数
? 运算符与 main 函数
main 函数的返回类型也可以是:Result
use std::error::Error as error;
fn main() -> Result<(), Box<dyn error>> {
let f = File::open("foo")?;
Ok(())
}
Box<dyn Error>
是 trait 对象,可以简单理解为“任何可能的错误类型”。
这样就可以在 main 函数中使用 ?
运算符了。
(开始玄学 o_o)
? 与 from 函数
Trait std:convert::From
上的 from 函数
总体原则
在定义一个可能失败的函数时,优先考虑返回 Result,若你觉得这个错误一定无法恢复,那就可以代替调用者调用 panic!
编写示例、原型代码、测试
可以使用panic!
有时你比编译器掌握更多的信息
你可以确定 Result 就是 Ok,那么可以使用 unwrap,例子如下:
use std::net::IpAddr;
fn test06() {
let home: IpAddr = "192.168.3.110".parse().unwrap();
}
这里我们可以确定这个 IP 地址解析出来一定是有效的,因此可以直接使用 unwrap。
错误处理的指导性建议
场景建议
为验证创建自定义类型
创建新的类型,把验证逻辑放在构造实例的函数里。
以第一节的猜数游戏为例:
fn main() {
loop {
//...
let guess = "32";
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
if guess < 1 || guess > 100 {
println!("The num must between 1 and 100");
continue;
}
//...
}
}
这样一个功能就是判断输入的数是否符合 i32 类型,若符合的话表达式返回 num,然后再判断是否在 1~100 之间,如果不满足则继续循环。如果有多个函数中都需要类似这样的判断,则代码便会显得冗余,我们可以自定义一个验证逻辑:
pub struct Guess {
value: i32
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("The guess value must between 1 and 100, got {}", value);
}
Guess {value}
}
//类似 getter 方法
pub fn value(&self) -> i32 {
self.value
}
}
fn guess_game() {
loop {
//...
let guess = "32";
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
let guess = Guess::new(guess);
}
}
如果能够成功创建 Guess 实例的话,那么就说明值通过了验证,而不需要将验证功能写在函数里了。
上述 value 方法是获得 Guess 结构体中的 value 字段值,因为结构体中的字段是私有的,外部无法直接对字段赋值。