18|错误处理:为什么Rust的错误处理与众不同?

225 阅读2分钟

开始学习

错误处理的主流方法

使用返回值(错误码)

使用返回值来表征错误

有个致命的问题:在调用者调用时,错误就必须得到处理或者显式的传播。

使用异常

把异常看成一种关注点分离(Separation of Concerns):错误的产生和错误的处理完全被分隔开,调用者不必关心错误,而被调者也不强求调用者关心错误

当异常安全无法保证时,程序的正确性会受到很大的挑战

使用类型系统

通过类型来表征错误:使用一个内部包含正常返回类型和错误返回类型的复合类型

Rust 的错误处理

Option 和 Result

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


pub enum Option<T> {
    None,
    Some(T),
}

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

// 编译器会对有 must_use 标注的所有类型做特殊处理:如果该类型对应的值没有被显式使用,则会告警
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

? 操作符

  1. 只想传播错误,不想就地处理,可以用 ? 操作符

  2. ? 操作符内部被展开成类似这样的代码:


match result {
  Ok(v) => v,
  Err(e) => return Err(e.into())
}

函数式错误处理

Rust 还为 Option 和 Result 提供了大量的辅助函数,如 map / map_err / and_then

image.png

panic! 和 catch_unwind

  1. 在使用 Option 和 Result 类型时,开发者也可以对其 unwarp() 或者 expect(),强制把 Option 和 Result 转换成 T,如果无法完成这种转换,也会 panic! 出来
  2. panic! 是不可恢复或者不想恢复的错误,程序终止运行并得到崩溃信息
  3. Rust 标准库下提供了catch_unwind() ,把调用栈回溯到 catch_unwind 这一刻,作用和其它语言的 try {…} catch {…} 一样

Error trait 和错误类型的转换

  1. 可以使用 thiserror和 anyhow来简化定义Error trait
  2. thiserror 提供了一个派生宏(derive macro)来简化错误类型的定义
  3. anyhow 实现了 anyhow::Error 和任意符合 Error trait 的错误类型之间的转换,让你可以使用 ? 操作符,不必再手工转换错误类型

链接

  1. 异常安全
  2. 避免抛出异常
  3. Golang使用方式值处理异常
  4. railway-oriented-programming
  5. noise protocol
  6. Erlang NIF
  7. thiserror
  8. anyhow