错误处理

1. Option 和 Result

Option 是一个 enum, 它可以承载有值 / 无值这种最简单的错误类型.

其定义如下:

#![allow(unused)]
fn main() {
pub enum Option<T> {
    None,
    Some(T),

}

Result 是一个更加复杂的 enum, 当函数出错时, 可以返回 Err(E), 否则 Ok(T).

其定义如下:

#![allow(unused)]
fn main() {
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

2. ? 操作符

好在 Rust 除了有强大的类型系统外, 还具备元编程的能力, 所以在 Rust 代码中, 如果你只想传播错误, 不想就地处理, 可以用 ? 操作符.

? 操作符内部被展开成类似下面的代码:

#![allow(unused)]
fn main() {
match result {
  Ok(v) => v,
  Err(e) => return Err(e.into())
}
}

在不同的错误类型之间是无法直接使用的, 注意上面的 e.into(), 需要实现 From trait 在二者之间建立起转换的桥梁

3. 函数式错误处理

Rust 还为 OptionResult 提供了大量的辅助函数, 如 map / map_err / and_then, 你可以很方便地处理数据结构中部分情况. 如下图所示:

image-20230115183216477

通过这些函数, 你可以很方便地对错误处理引入Railroad oriented programming 范式

4. panic! 和 catch_unwind

panic! 是不可恢复或者不想恢复的错误, 我们希望在此刻, 程序终止运行并得到崩溃信息.

Rust 标准库下提供了 catch_unwind() , 把调用栈回溯到 catch_unwind 这一刻你就可以把 Rust 代码整个封装在 catch_unwind() 函数所需要传入的闭包中. 这样, 一旦任何代码中, 包括第三方 crates 的代码, 含有能够导致 panic! 的代码, 都会被捕获, 并被转换为一个 Result.

5. Error trait 和 错误的转换

为了规范这个代表错误的数据类型的行为, Rust 定义了 Error trait:

#![allow(unused)]
fn main() {
pub trait Error: Debug + Display {
    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
    fn backtrace(&self) -> Option<&Backtrace> { ... }
    fn description(&self) -> &str { ... }
    fn cause(&self) -> Option<&dyn Error> { ... }
}
}

我们可以使用 thiserroranyhow 来简化这个步骤. thiserror 提供了一个派生宏(derive macro)来简化错误类型的定义.