Rust 错误处理

  |   0 评论   |   0 浏览

在编程语言中,处理错误的方式大致可分为两种:

  • 抛出异常
  • 作为值返回(返回一个错误代码或者自定义错误类型)

Rust 将错误作为值返回,并提供了原生的错误处理方案。在 Rust 中,错误可分为两种:

  • 可恢复错误(recoverable)—— Result<T, E>
  • 不可恢复错误(unrecoverable)—— panic!

panic!

panic! 是 Rust 提供的一个宏,这个宏在执行时会打印错误信息,展开并清理栈数据。

panic! 会导致当前线程结束,甚至是整个程序的结束。因此,panic! 通常适合用在编写的示例或者测试代码中。

示例:直接调用 panic!

1fn main() {
2    panic!("crash and burn");
3}

当出现 panic! 时,程序默认会展开(unwinding),即:程序会回溯栈并清理它遇到的每个函数的数据。另外,也可以选择直接终止(abort),不清理数据就退出程序,占用的内存需要操作系统来清理。如果要选择直接终止,需要在 Cargo.toml 文件中添加下面的配置:

1# 在这样的配置下,项目生成的二进制文件会比较小。
2[profile.release]
3panic = 'abort'

栈回溯信息

当程序遇到 panic! 退出时,它会输出错误信息(即:panic!() 括号里的内容),以及发生错误的位置。并且它会提示你如何查看栈回溯(backtrace)信息。

示例:制造一个不可恢复错误

1fn main() {
2    let v = vec![1, 2, 3];
3
4    v[99];
5}

运行上面的代码:

1PS D:\vscode\rust_demo\hello> cargo run
2   Compiling hello v0.1.0 (D:\vscode\rust_demo\hello)
3    Finished dev [unoptimized + debuginfo] target(s) in 1.18s
4     Running `target\debug\hello.exe`
5thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5
6note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
7error: process didn't exit successfully: `target\debug\hello.exe` (exit code: 101)
8PS D:\vscode\rust_demo\hello>

输出信息提示我们可以设置 RUST_BACKTRACE 环境变量来显示栈回溯信息。按照提示,重新运行程序,我们会看到详细的栈回溯信息:

 1PS D:\vscode\rust_demo\hello> $Env:RUST_BACKTRACE=1
 2PS D:\vscode\rust_demo\hello>
 3PS D:\vscode\rust_demo\hello> cargo run
 4    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
 5     Running `target\debug\hello.exe`
 6thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5
 7stack backtrace:
 8   0: std::panicking::begin_panic_handler
 9             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\std\src\panicking.rs:475
10   1: core::panicking::panic_fmt
11             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\core\src\panicking.rs:85
12   2: core::panicking::panic_bounds_check
13             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\core\src\panicking.rs:62
14   3: core::slice::{{impl}}::index<i32>
15             at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\mod.rs:3282
16   4: core::slice::{{impl}}::index<i32,usize>
17             at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\mod.rs:3123
18   5: alloc::vec::{{impl}}::index<i32,usize>
19             at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\alloc\src\vec.rs:2000
20   6: hello::main
21             at .\src\main.rs:4
22   7: core::ops::function::FnOnce::call_once<fn(),tuple<>>
23             at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:227
24note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
25error: process didn't exit successfully: `target\debug\hello.exe` (exit code: 101)
26PS 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 字符,并返回该字符的索引

1// 返回值是 `Option<usize>`,用 `None` 来表示没有找到 needle 字符
2fn find(haystack: &str, needle: char) -> Option<usize> {
3    for (offset, c) in haystack.char_indices() {
4        if c == needle {
5            return Some(offset);
6        }
7    }
8    None
9}

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 类型的默认值。

示例:查找文件名中的扩展名

 1fn extension_explicit(file_name: &str) -> Option<&str> {
 2    // 模式匹配
 3    match find(file_name, '.') {
 4        None => None,
 5        Some(i) => Some(&file_name[i+1..]),
 6    }
 7}
 8fn main() {
 9    // 模式匹配
10    match extension_explicit("foo.rs") {
11        None => println!("no extension"),
12        Some(ext) => assert_eq!(ext, "rs"),
13    }
14
15    // 若遇到 `None`,则直接 panic
16    let extension = extension_explicit("foo.rs").unwrap();
17    println!("extension: {}", extension);
18
19    // 若遇到 `None`,则返回指定的默认值:`rs`
20    let extension = extension_explicit("foo").unwrap_or("rs");
21    println!("extension: {}", extension);
22
23    // 若遇到 `None`,则返回 &str 的默认值
24    let extension = extension_explicit("foo").unwrap_or_default();
25    println!("extension: {}", extension);
26}

map

Option<T>map 函数用来将一个枚举类型 Option<T> 映射为另外一个枚举类型 Option<U>

map 的参数是一个闭包,用来指明如何映射。

示例:查找文件名中的扩展名

1// 使用 `map` 函数可以替代模式匹配,这种方式更简洁
2fn extension(file_name: &str) -> Option<&str> {
3    // 若有值 `Some(T)` 则会执行 `map` 里的闭包,否则直接返回 `None`
4    find(file_name, '.').map(|i| &file_name[i+1..])
5}

map 类似的一些函数:

  • map_or -- 若枚举值是 Some(T),则运行闭包,否则返回指定的默认值
  • map_or_else -- 该函数有两个闭包参数。若枚举值是 Some(T),则运行第一个闭包,否则运行第二个闭包。

Result

Result<T, E> 是 Rust 标准库提供的一个枚举类型,其定义如下:

1pub enum Result<T, E> {
2    Ok(T),
3    Err(E),
4}

Result<T, E> 可以看做是 Option<T> 的升级版本,当值缺失时,Option<T> 使用 None 表示值不存在,而 Result<T, E> 使用 Err(E) 描述错误信息。Err(E) 中的 E 既可以是 Rust 中现有的类型,也可以是我们自定义的类型。

示例:在字符串 haystack 中查找 needle 字符,并返回该字符的索引

1// 若未找到则返回 `Err(&str)` ,其中包含错误信息
2fn find(haystack: &str, needle: char) -> Result<usize, &str> {
3    for (offset, c) in haystack.char_indices() {
4        if c == needle {
5            return Ok(offset);
6        }
7    }
8    Err("The specified character is not found")
9}

unwrapexpect

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 类型的默认值。

示例;查找文件名中的扩展名

 1fn extension_explicit(file_name: &str) -> Result<&str, &str> {
 2    match find(file_name, '.') {
 3        Err(e) => Err(e),
 4        Ok(i) => Ok(&file_name[i + 1..]),
 5    }
 6}
 7fn main() {
 8    // 模式匹配
 9    match extension_explicit("foo.rs") {
10        Err(e) => println!("no extension: {}", e),
11        Ok(ext) => assert_eq!(ext, "rs"),
12    }
13
14    // 若遇到 `Err`,则直接 panic
15    let extension = extension_explicit("foo.rs").unwrap();
16    println!("extension: {}", extension);
17
18    // 若遇到 `Err`,则返回指定的默认值:`rs`
19    let extension = extension_explicit("foo").unwrap_or("rs");
20    println!("extension: {}", extension);
21
22    // 若遇到 `Err`,则返回 &str 的默认值:空切片
23    let extension = extension_explicit("foo").unwrap_or_default();
24    println!("extension: {}", extension);
25}

Result<T, E>expect 函数与 unwrap 类似,该函数返回 Ok(T) 包含的值。若枚举值是 Err(E),则会 panic ,除了会输出 Err(E) 中的内容,还会输出 expect 参数内容。

示例

1let x: Result<u32, &str> = Err("emergency failure");
2x.expect("Testing expect"); // panics with `Testing expect: emergency failure`

map

Option<T>map 函数一样,Result<T, E>map 函数可以简化模式匹配。

示例

1// 使用 map 函数替代模式匹配,这种方式更简洁
2fn extension(file_name: &str) -> Result<&str, &str> {
3    // 若返回值是 `Ok` 则会执行 `map` 里的闭包,否则直接返回 `Err`
4    find(file_name, '.').map(|i| &file_name[i + 1..])
5}

Result 别名

Result<T, E> 中的 T 或者 E 是固定的类型时,我们可以给 Result<T, E> 取一个别名,这样可以减少重复编码。

示例:标准库中的 std::io::Result

 1// 这是标准库 `io` 模块中给 `Result<T, std:🇮🇴:Error>` 定义的一个别名
 2// 其中 `std:🇮🇴:Error` 是 io 模块中定义的一个通用的错误类型
 3type Result<T> = Result<T, std::io::Error>;
 4
 5// 使用别名,比如在 `std:🇮🇴:Read` Trait 中
 6pub trait Read {
 7    // `Result<usize>` 等同于 `Result<usize, std:🇮🇴:Error>`
 8    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
 9    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { ... }
10    fn is_read_vectored(&self) -> bool { ... }
11    unsafe fn initializer(&self) -> Initializer { ... }
12    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
13    fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
14    // `Result<()>` 等同于 `Result<(), std:🇮🇴:Error>`
15    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
16    ...
17}

传递错误

Rust 中的传递错误(Propagating Errors)类似于 Java 中的抛出异常,就是在当前函数中遇到错误时不做处理,而是将这个错误返回,让该函数的调用者决定如何处理该错误。

示例:将两个字符串解析为数值,并返回两个数值的乘积

 1use std::num::ParseIntError;
 2
 3fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
 4    // 此处遇到错误,不做处理直接返回,把错误抛给上层该函数的调用者
 5    // 解析出错是因为传入该函数的参数有问题,应该由函数的调用者来处理
 6    let first_number = match first_number_str.parse::<i32>() {
 7        Ok(first_number) => first_number,
 8        Err(e) => return Err(e), // 这里使用 `return` 提前返回 `Err`
 9    };
10
11    let second_number = match second_number_str.parse::<i32>() {
12        Ok(second_number) => second_number,
13        Err(e) => return Err(e),
14    };
15
16    Ok(first_number * second_number)
17}

简化传递错误

使用 ? 操作符可以简化错误的传递,? 操作符的作用是:返回 Result<T, E>T 类型的值,若遇到错误则返回 E

示例:将两个字符串解析为数值,并返回两个数值的乘积

1use std::num::ParseIntError;
2
3fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
4    let first_number = first_number_str.parse::<i32>()?;
5
6    let second_number = second_number_str.parse::<i32>()?;
7
8    Ok(first_number * second_number)
9}

相关资料

The Rust Programming Language

Error Handling in Rust

Rust by Example

Rust Primer