Rust中没有异常的概念,也没有其他语言中 try ... catch ... 的语法结构。取而代之的是每个可能会产生错误的函数和方法都需要返回一个 Result 类型来提供返回值和错误信息。如果一个函数或者方法返回了 Result 类型的值,那么就代表这个函数或方法可能会执行失败,必须要对其可能发生的错误进行处理。

这里需要注意的是,Rust程序没有一个异常状态,取而代之的是一个panic状态。在进入panic状态以后,程序的默认动作是展开栈,当然也可以选择中止进程。在程序中调用宏 panic!() 可以手工触发panic状态,其用法与宏 println!() 类似。panic状态并不是程序的崩溃状态,不违反Rust的任何安全原则,并且panic是线程级别的,在一个线程进入panic状态时,其他的线程不会受到影响。

Result 类型的常见返回类型为 Result<T, io::Error> ,在这个返回类型中,可以使用 Ok() 来获取类型为 T 的返回值,使用 Err() 来获取错误结果。

Result 类型的定义

Rust标准库中的 Result 类型实际上是一个泛型枚举类型,其定义如下:

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

Result 类型还有一种别名的写法:Result<T> ,它相当于 Result<T, std::io::Error> 。这种写法可以在一个模块中避免连续重复多次书写同一个错误类型。

错误的捕获

既然 Result 类型是一个枚举类型,那么处理其中的内容就可以使用 match 表达式了。这时的 match 表达式相当与其他语言中的 try ... catch ... 语法结构。其用法也很简单:

  1. match something_may_wrong() {
  2. Ok(report) => {
  3. // 处理正常运行的返回值,返回值在变量report里
  4. }
  5. Err(err) => {
  6. // 处理出现错误时的错误信息,错误信息在变量err里
  7. }
  8. }

对于 match 表达式的啰嗦,Rust也针对一些常见用法提供了快速处理函数可供使用。

  • result.is_ok()result.is_err() ,返回一个 bool 值,用于判断 result 是成功的还是失败的。
  • result.ok() ,返回 Option<T> 类型的成功值,如果 result 是成功的,那么可以使用 Some(value) 获取返回值;否则会返回 None 丢弃错误值。
  • result.err() ,返回 Option<E> 类型的错误值,行为与 result.ok() 正好相反。
  • result.unwrap_or(default) ,如果 result 是成功的,会返回类型为 T 的返回值,而不是 Option<T> 类型;如果 result 是失败的,则会返回给定的默认值,同时丢弃错误值。
  • result.unwrap_or_else(fallback_fn) ,如果 result 是成功的,会返回类型为 T 的返回值;如果 result 是失败的,则会使用给定的函数或者闭包生成一个后备值,这个函数或者闭包会接受 result 中携带的错误值作为参数。
  • result.unwrap() ,如果 result 是成功的,会返回类型为 T 的返回值;否则会使所在线程进入panic状态。
  • result.expect(message) ,如果 result 是成功的,会返回类型为 T 的返回值;否则需要提供一个进入panic状态后输出在控制台的错误信息。
  • result.as_ref() ,将类型 Result<T, E> 转换为 Result<&T, &E> ,可直接借用目前 result 中的成功值和错误值的引用。
  • result.as_mut() ,将类型 Result<T, E> 转换为 Result<&mut T, &mut E>

    错误输出

    Result 类型中携带的错误信息可以直接使用 println!() 来输出到控制台,例如 :

    1. match some_result {
    2. Ok(report) => {}
    3. Err(err) => {
    4. println!("Error occured: {}", err);
    5. }
    6. }

    除此之外,还可以调用以下两个方法来获取更多的信息。

  • err.description() ,返回 &str 类型的错误信息。

  • err.cause() ,返回一个 Option<&Error>

    错误传播

    在大多数情况下,错误不一定都需要在它发生的地方处理,这样就需要将错误抛至上一级调用进行处理了。这个就像是Java中的 throws
    1. public void throw_to_uplevel() throws IOError {
    2. // ...
    3. throw new IOError("Some Error");
    4. // ...
    5. }
    在Rust中把函数中返回的 Result<T, E> 抛出到上一级,只需要在函数调用时添加一个 ? 操作符即可。这样,当函数调用返回错误时,Rust就会把错误信息沿着调用链向上传播。例如:
    1. let result = may_be(something)?;
    这里需要注意的是,如果已经到达程序的 main() 函数,那么就不能继续向上传播错误了,如果需要程序停下来,可以直接使用 .expect() 。或者可以使用以下代码输出更加有亲和力的错误信息。
    1. fn main() {
    2. if let Err(err) = maybe_someerror() {
    3. print_error(&err);
    4. std::process::exit(1);
    5. }
    6. }

    栈展开

    在一个线程进入到panic状态以后,可以使用标准库提供的函数 std::panic::catch_unwind() 来捕获栈展开。通过使用标准库 std::panic 中的函数,可以在线程进入到panic状态的时候恢复线程的运行。

栈展开和恢复线程运行的操作在调用C/C++或者其他语言协程的库的时候是十分必要的,因为其他语言编写的库,不会遵守Rust的安全原则。