参考章节《Rust 程序设计语言》第9章 错误处理
Rust 将错误分为两大类:可恢复的(recoverable)和 不可恢复的(unrecoverable)错误。这一点和Go有点类似
对于一个不可恢复的错误,我们一般认为是一个bug
,因此我们要立即停止程序。
一种是我们自己手动触发的painc
,可以使用 painc!
宏
1
2
3
|
fn main() {
panic!("crash and burn"); // 手动触发一个painc,panic会终止程序,和golang的panic类似
}
|
运行程序将会出现类似这样的输出:
1
2
3
4
5
6
|
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
还有一种是程序 bug
引起的 painc
1
2
3
4
|
fn main() {
let v = vec![1, 2, 3];
println!("{}", v[99]);
}
|
小小的总结一下
- 说白了,
painc
的目的是终止程序,
painc
默认会清理堆栈,可参考书中这一小节 对应 panic 时的栈展开或终止
- 通过设置环境变量
RUST_BACKTRACE=1
(非0就行) 打印堆栈的详细调用过程
对于一个可恢复的错误,我们很可能只想向用户报告问题并重试操作。
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数可能会让我们对某种预期的错误做出反应
Rust为我们提供了一个 Result<T, E>
枚举
1
2
3
4
|
enum Result<T, E> {
Ok(T),
Err(E),
}
|
T
和 E
是泛型类型参数
;后面会学习到它。现在你需要知道的就是 T
代表成功时返回的 Ok
成员中的数据的类型,而 E
代表失败时返回的 Err
成员中的错误的类型。
- 我们来看一个简单的处理
Result<T, E>
的例子
1
2
3
4
5
6
7
8
9
10
|
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error), // 发生错误时,终止程序并打印错误消息
};
}
|
File::open
函数的返回值类型是 Result<T, E>
。
这里泛型参数 T
放入了成功值的类型 std::fs::File
,它是一个文件句柄。
E
被用在失败值上时 E
的类型是 std::io::Error
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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") {
// 我们感兴趣的成员是 ErrorKind::NotFound,它代表尝试打开的文件并不存在。
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}
|
io::Error
有一个返回 io::ErrorKind
值的 kind
方法可供调用。
io::ErrorKind
是一个标准库提供的枚举,它的成员对应 io
操作可能导致的不同错误类型。
我们可以通过 unwrap_or_else
方法并传递一个闭包
,来稍微简化上面的写法,虽然我知道闭包
是什么,但我不想在这一节写上它,因为它超出了这一节的范畴
- 失败时
panic
的简写:unwrap
和 expect
如果 Result 值是成员 Ok,unwrap 会返回 Ok 中的值。如果 Result 是成员 Err,unwrap 会为我们调用 panic!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
use std::fs::File;
fn main() {
let f = File::open("text.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error), // 发生错误时,终止程序并打印错误消息
};
// 上面的可以简写成下面的格式
File::open("text.txt").unwrap();
// 还有另一个类似于 unwrap 的方法它还允许我们选择 panic! 的错误信息:
File::open("text.txt").expect("Problem opening the file");
}
|
- 接下来我们来看一下,我们自己如何返回一个
Result
我们在编写函数或方法时,我们明确知道,我们这个函数或方法可能会发生一个错误,而这个错误是调用者可以处理的,它不是一个无法处理的错误
那么我们就可以让函数或方法返回 Result 类型,这样调用者就知道,你这个函数或方法可能会发生错误,需要他来处理
第一种方法,最苯的方法,不推荐
通过 match
关键字匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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), // 失败返回io::Error,可以通过 return语句 提前返回
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e), // 失败返回io::Error
}
}
|
第二种方法,?
号元算符简化
需要注意的是 ?
运算符只能在 返回Result
的函数中使用
1
2
3
4
5
6
7
8
|
fn read_username_from_file() -> Result<String, io::Error> {
// File::open 调用结尾的 ? 将会把 Ok 中的值返回给变量 f。
// 如果出现了错误,? 运算符会提早返回整个函数并将一些 Err 值传播给调用者。
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?; // 同理也适用于 read_to_string 调用结尾的 ?。
Ok(s) // 把s包装到Ok成员中返回,因为返回值类型是 Result,这句话要是看不懂的话,你可以去面壁思过了
}
|
上面的例子,当 ?
号运算符所在的语句发生错误时,Err
中的值将作为整个函数的返回值,就好像使用了 return
关键字一样,这样错误值就被传播给了调用者。
还可以再简化一下
1
2
3
4
5
6
|
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)
}
|
painc
代表不可恢复错误,这通常是程序发生了bug
, 发生 painc
时程序会终止,通过 painc!
宏可以手动触发 painc
Result<T, E>
代表可恢复的错误,其中 T
代表成功时返回的 Ok
成员中的数据的类型,而 E
代表失败时返回的 Err
成员中的错误的类型。
io::Error
有一个返回 io::ErrorKind
值的 kind
方法可供调用,为我们提供了获取更具体的错误类型的能力
unwrap
如果 Result
值是成员 Ok
,unwrap
会返回 Ok
中的值。如果 Result
是成员 Err
,unwrap
会为我们调用 panic!
expect
和 unwrap
功能一样,只不过 expect
可以提供自定义信息
?
号运算符可以简化代码,?
号运算符只能在 返回Result
的函数中使用
?
号运算符可以链式调用