在编程语言中,处理错误的方式大致可分为两种:
- 抛出异常
- 作为值返回(返回一个错误代码或者自定义错误类型)
Rust 将错误作为值返回,并提供了原生的错误处理方案。在 Rust 中,错误可分为两种:
- 可恢复错误(recoverable)——
Result<T, E>
- 不可恢复错误(unrecoverable)——
panic!
panic!
panic!
是 Rust 提供的一个宏,这个宏在执行时会打印错误信息,展开并清理栈数据。
panic!
会导致当前线程结束,甚至是整个程序的结束。因此,panic!
通常适合用在编写的示例或者测试代码中。
示例:直接调用 panic!
宏
fn main() {
panic!("crash and burn");
}
当出现 panic!
时,程序默认会展开(unwinding),即:程序会回溯栈并清理它遇到的每个函数的数据。另外,也可以选择直接终止(abort),不清理数据就退出程序,占用的内存需要操作系统来清理。如果要选择直接终止,需要在 Cargo.toml
文件中添加下面的配置:
# 在这样的配置下,项目生成的二进制文件会比较小。
[profile.release]
panic = 'abort'
栈回溯信息
当程序遇到 panic!
退出时,它会输出错误信息(即:panic!()
括号里的内容),以及发生错误的位置。并且它会提示你如何查看栈回溯(backtrace)信息。
示例:制造一个不可恢复错误
fn main() {
let v = vec![1, 2, 3];
v[99];
}
运行上面的代码:
PS D:\vscode\rust_demo\hello> cargo run
Compiling hello v0.1.0 (D:\vscode\rust_demo\hello)
Finished dev [unoptimized + debuginfo] target(s) in 1.18s
Running `target\debug\hello.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\hello.exe` (exit code: 101)
PS D:\vscode\rust_demo\hello>
输出信息提示我们可以设置 RUST_BACKTRACE
环境变量来显示栈回溯信息。按照提示,重新运行程序,我们会看到详细的栈回溯信息:
PS D:\vscode\rust_demo\hello> $Env:RUST_BACKTRACE=1
PS D:\vscode\rust_demo\hello>
PS D:\vscode\rust_demo\hello> cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target\debug\hello.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5
stack backtrace:
0: std::panicking::begin_panic_handler
at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\std\src\panicking.rs:475
1: core::panicking::panic_fmt
at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\core\src\panicking.rs:85
2: core::panicking::panic_bounds_check
at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\core\src\panicking.rs:62
3: core::slice::{{impl}}::index<i32>
at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\mod.rs:3282
4: core::slice::{{impl}}::index<i32,usize>
at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\mod.rs:3123
5: alloc::vec::{{impl}}::index<i32,usize>
at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\alloc\src\vec.rs:2000
6: hello::main
at .\src\main.rs:4
7: core::ops::function::FnOnce::call_once<fn(),tuple<>>
at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: process didn't exit successfully: `target\debug\hello.exe` (exit code: 101)
PS D:\vscode\rust_demo\hello>
上面的示例是在 Windows 系统中运行的,设置环境变量的方式与 Linux 不同。若使用 CMD ,则执行 set RUST_BACKTRACE=1
命令。若使用 PowerShell ,则执行 $Env:RUST_BACKTRACE=1
命令。
Option
Option<T>
是 Rust 标准库提供的一个枚举类型,它只有两个成员:
- Some(T) – 包含一个 T 类型的值
- None – 表示没有值
当使用 Option<T>
作为函数的参数或者返回值类型时,函数的创建者或调用者必须处理枚举值是 None
的情况,这样可以减少潜在的 bug。
示例:在字符串 haystack
中查找 needle
字符,并返回该字符的索引
// 返回值是 `Option<usize>`,用 `None` 来表示没有找到 needle 字符
fn find(haystack: &str, needle: char) -> Option<usize> {
for (offset, c) in haystack.char_indices() {
if c == needle {
return Some(offset);
}
}
None
}
unwrap
Option<T>
的 unwrap
函数返回 Some(T)
中的 T 类型值。若枚举值是 None
,则会 panic。因此,该函数一般不被推荐使用。应该使用模式匹配对 None
值进行处理,或者使用下面三个函数:
unwrap_or
– 返回Some(T)
包含的值或者返回指定的默认值。unwrap_or_else
– 返回Some(T)
包含的值或者通过一个闭包计算。unwrap_or_default
– 返回Some(T)
包含的值或者 T 类型的默认值。
示例:查找文件名中的扩展名
fn extension_explicit(file_name: &str) -> Option<&str> {
// 模式匹配
match find(file_name, '.') {
None => None,
Some(i) => Some(&file_name[i+1..]),
}
}
fn main() {
// 模式匹配
match extension_explicit("foo.rs") {
None => println!("no extension"),
Some(ext) => assert_eq!(ext, "rs"),
}
// 若遇到 `None`,则直接 panic
let extension = extension_explicit("foo.rs").unwrap();
println!("extension: {}", extension);
// 若遇到 `None`,则返回指定的默认值:`rs`
let extension = extension_explicit("foo").unwrap_or("rs");
println!("extension: {}", extension);
// 若遇到 `None`,则返回 &str 的默认值
let extension = extension_explicit("foo").unwrap_or_default();
println!("extension: {}", extension);
}
map
Option<T>
的 map
函数用来将一个枚举类型 Option<T>
映射为另外一个枚举类型 Option<U>
。
map
的参数是一个闭包,用来指明如何映射。
示例:查找文件名中的扩展名
// 使用 `map` 函数可以替代模式匹配,这种方式更简洁
fn extension(file_name: &str) -> Option<&str> {
// 若有值 `Some(T)` 则会执行 `map` 里的闭包,否则直接返回 `None`
find(file_name, '.').map(|i| &file_name[i+1..])
}
和 map
类似的一些函数:
map_or
– 若枚举值是Some(T)
,则运行闭包,否则返回指定的默认值map_or_else
– 该函数有两个闭包参数。若枚举值是Some(T)
,则运行第一个闭包,否则运行第二个闭包。
Result
Result<T, E>
是 Rust 标准库提供的一个枚举类型,其定义如下:
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Result<T, E>
可以看做是 Option<T>
的升级版本,当值缺失时,Option<T>
使用 None
表示值不存在,而 Result<T, E>
使用 Err(E)
描述错误信息。Err(E)
中的 E
既可以是 Rust 中现有的类型,也可以是我们自定义的类型。
示例:在字符串 haystack
中查找 needle
字符,并返回该字符的索引
// 若未找到则返回 `Err(&str)` ,其中包含错误信息
fn find(haystack: &str, needle: char) -> Result<usize, &str> {
for (offset, c) in haystack.char_indices() {
if c == needle {
return Ok(offset);
}
}
Err("The specified character is not found")
}
unwrap 和 expect
Result<T, E>
的 unwrap
函数返回 Ok(T)
包含的值。若枚举值是 Err(E)
,则会 panic。
unwrap
函数通常不推荐使用,应该使用模式匹配或者下面的函数来处理报错情况:
unwrap_or
– 返回Ok(T)
包含的值或者指定的默认值。unwrap_or_else
– 返回Ok(T)
包含的值或者通过一个闭包计算。unwrap_or_default
– 返回Ok(T)
包含的值或者 T 类型的默认值。
示例;查找文件名中的扩展名
fn extension_explicit(file_name: &str) -> Result<&str, &str> {
match find(file_name, '.') {
Err(e) => Err(e),
Ok(i) => Ok(&file_name[i + 1..]),
}
}
fn main() {
// 模式匹配
match extension_explicit("foo.rs") {
Err(e) => println!("no extension: {}", e),
Ok(ext) => assert_eq!(ext, "rs"),
}
// 若遇到 `Err`,则直接 panic
let extension = extension_explicit("foo.rs").unwrap();
println!("extension: {}", extension);
// 若遇到 `Err`,则返回指定的默认值:`rs`
let extension = extension_explicit("foo").unwrap_or("rs");
println!("extension: {}", extension);
// 若遇到 `Err`,则返回 &str 的默认值:空切片
let extension = extension_explicit("foo").unwrap_or_default();
println!("extension: {}", extension);
}
Result<T, E>
的 expect
函数与 unwrap
类似,该函数返回 Ok(T)
包含的值。若枚举值是 Err(E)
,则会 panic ,除了会输出 Err(E)
中的内容,还会输出 expect
参数内容。
示例:
let x: Result<u32, &str> = Err("emergency failure");
x.expect("Testing expect"); // panics with `Testing expect: emergency failure`
map
与 Option<T>
的 map
函数一样,Result<T, E>
的 map
函数可以简化模式匹配。
示例:
// 使用 map 函数替代模式匹配,这种方式更简洁
fn extension(file_name: &str) -> Result<&str, &str> {
// 若返回值是 `Ok` 则会执行 `map` 里的闭包,否则直接返回 `Err`
find(file_name, '.').map(|i| &file_name[i + 1..])
}
Result 别名
当 Result<T, E>
中的 T
或者 E
是固定的类型时,我们可以给 Result<T, E>
取一个别名,这样可以减少重复编码。
示例:标准库中的 std::io::Result
// 这是标准库 `io` 模块中给 `Result<T, std::io::Error>` 定义的一个别名
// 其中 `std::io::Error` 是 io 模块中定义的一个通用的错误类型
type Result<T> = Result<T, std::io::Error>;
// 使用别名,比如在 `std::io::Read` Trait 中
pub trait Read {
// `Result<usize>` 等同于 `Result<usize, std::io::Error>`
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { ... }
fn is_read_vectored(&self) -> bool { ... }
unsafe fn initializer(&self) -> Initializer { ... }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
// `Result<()>` 等同于 `Result<(), std::io::Error>`
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
...
}
传递错误
Rust 中的传递错误(Propagating Errors)类似于 Java 中的抛出异常,就是在当前函数中遇到错误时不做处理,而是将这个错误返回,让该函数的调用者决定如何处理该错误。
示例:将两个字符串解析为数值,并返回两个数值的乘积
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
// 此处遇到错误,不做处理直接返回,把错误抛给上层该函数的调用者
// 解析出错是因为传入该函数的参数有问题,应该由函数的调用者来处理
let first_number = match first_number_str.parse::<i32>() {
Ok(first_number) => first_number,
Err(e) => return Err(e), // 这里使用 `return` 提前返回 `Err`
};
let second_number = match second_number_str.parse::<i32>() {
Ok(second_number) => second_number,
Err(e) => return Err(e),
};
Ok(first_number * second_number)
}
简化传递错误
使用 ?
操作符可以简化错误的传递,?
操作符的作用是:返回 Result<T, E>
中 T
类型的值,若遇到错误则返回 E
。
示例:将两个字符串解析为数值,并返回两个数值的乘积
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
let first_number = first_number_str.parse::<i32>()?;
let second_number = second_number_str.parse::<i32>()?;
Ok(first_number * second_number)
}