fn main() {
panic!("crash and burn");
}
当出现 panic! 时,程序提供了两种方式来处理终止流程:栈展开和直接终止
先来一点背景知识,在前面章节我们粗略讲过 Result
enum Result {
Ok(T),
Err(E),
}
当没有错误发生时,函数返回一个用 Result 类型包裹的值 Ok(T),当错误时,返回一个 Err(E)。对于 Result 返回我们有很多处理方法,最简单粗暴的就是 unwrap 和 expect,这两个函数非常类似,我们以 unwrap 举例:
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();
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") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => panic!("Problem opening the file: {:?}", other_error),
},
};
}
上面代码在匹配出 error 后,又对 error 进行了详细的匹配解析,最终结果:
在不需要处理错误的场景,例如写原型、示例时,我们不想使用 match 去匹配 Result
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
如果调用这段代码时 hello.txt 文件不存在,那么 unwrap 就将直接 panic:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
expect 跟 unwrap 很像,也是遇到错误直接 panic, 但是会带上自定义的错误提示信息,相当于重载了错误打印的函数:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
报错如下:
thread 'main' panicked at 'Failed to open hello.txt: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
程序几乎不太可能只有 A->B 形式的函数调用,一个设计良好的程序,一个功能涉及十几层的函数调用都有可能。而错误处理也往往不是哪里调用出错,就在哪里处理,实际应用中,大概率会把错误层层上传然后交给调用链的上游函数进行处理,错误传播将极为常见。
例如以下函数从文件中读取用户名,然后将结果进行返回:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result {
// 打开文件,f是`Result<文件句柄,io::Error>`
let f = File::open("hello.txt");
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};
// 创建动态字符串s
let mut s = String::new();
// 从f文件句柄读取数据并写入s中
match f.read_to_string(&mut s) {
// 读取成功,返回Ok封装的字符串
Ok(_) => Ok(s),
// 将错误向上传播
Err(e) => Err(e),
}
}
有几点值得注意:
。
传播界的大明星: ?
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
看到没,这就是排面,相比前面的 match 处理错误的函数,代码直接减少了一半不止.
其实 ? 就是一个宏,它的作用跟上面的 match 几乎一模一样:
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};
如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。
虽然 ? 和 match 功能一致,但是事实上 ? 会更胜一筹。
想象一下,一个设计良好的系统中,肯定有自定义的错误特征,错误之间很可能会存在上下级关系,例如标准库中的 std::io::Error 和 std::error::Error,前者是 IO 相关的错误结构体,后者是一个最最通用的标准错误特征,同时前者实现了后者,因此 std::io::Error 可以转换为 std:error::Error。
明白了以上的错误转换,? 的更胜一筹就很好理解了,它可以自动进行类型提升(转换):
fn open_file() -> Result> {
let mut f = File::open("hello.txt")?;
Ok(f)
}
上面代码中 File::open 报错时返回的错误是 std::io::Error 类型,但是 open_file 函数返回的错误类型是 std::error::Error 的特征对象,可以看到一个错误类型通过 ? 返回后,变成了另一个错误类型,这就是 ? 的神奇之处。
根本原因是在于标准库中定义的 From 特征,该特征有一个方法 from,用于把一个类型转成另外一个类型,? 可以自动调用该方法,然后进行隐式类型转换。因此只要函数返回的错误 ReturnError 实现了 From 特征,那么 ? 就会自动把 OtherError 转换为 ReturnError。
这种转换非常好用,意味着你可以用一个大而全的 ReturnError 来覆盖所有错误类型,只需要为各种子错误类型实现这种转换即可。
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? ? 还能实现链式调用,File::open 遇到错误就返回,没有错误就将 Ok 中的值取出来用于下一个方法调用,简直太精妙了.
use std::fs;
use std::io;
fn read_username_from_file() -> Result {
// read_to_string是定义在std::io中的方法,因此需要在上面进行引用
fs::read_to_string("hello.txt")
}
从文件读取数据到字符串中,是比较常见的操作,因此 Rust 标准库为我们提供了 fs::read_to_string 函数,该函数内部会打开一个文件、创建 String、读取文件内容最后写入字符串并返回,因为该函数其实与本章讲的内容关系不大,因此放在最后来讲,其实只是我想震你们一下
? 不仅仅可以用于 Result 的传播,还能用于 Option 的传播,再来回忆下 Option 的定义:
pub enum Option {
Some(T),
None
}
Result 通过 ? 返回错误,那么 Option 就通过 ? 返回 None:
fn first(arr: &[i32]) -> Option<&i32> {
let v = arr.get(0)?;
Some(v)
}
初学者在用 ? 时,老是会犯错,例如写出这样的代码:
fn first(arr: &[i32]) -> Option<&i32> {
arr.get(0)?
}
这段代码无法通过编译,切记:? 操作符需要一个变量来承载正确的值,这个函数只会返回 Some(&i32) 或者 None,只有错误值能直接返回,正确的值不行,所以如果数组中存在 0 号元素,那么函数第二行使用 ? 后的返回类型为 &i32 而不是 Some(&i32)。因此 ? 只能用于以下形式:
let v = xxx()?;
xxx()?.yyy()?;
在了解了 ? 的使用限制后,这段代码你很容易看出它无法编译:
use std::fs::File;
fn main() {
let f = File::open("hello.txt")?;
}
运行后会报错:
$ cargo run
...
the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:48
|
3 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
4 | let greeting_file = File::open("hello.txt")?;
| ^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
因为 ? 要求 Result
实际上 Rust 还支持另外一种形式的 main 函数:
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box> {
let f = File::open("hello.txt")?;
Ok(())
}
这样就能使用 ? 提前返回了,同时我们又一次看到了Box 特征对象,因为 std::error:Error 是 Rust 中抽象层次最高的错误,其它标准库中的错误都实现了该特征,因此我们可以用该特征对象代表一切错误,就算 main 函数中调用任何标准库函数发生错误,都可以通过 Box