Back
Featured image of post Rust 学习笔记05 - 错误处理

Rust 学习笔记05 - 错误处理

导航页

Rust 将错误分为两类:可恢复的(recoverable)、不可恢复的(unrecoverable)。

对于一个可恢复的错误,比如未找到文件,我们很能想告知用户,并让其重试。不可恢复的错误总是出现 bug 的征兆,比如数组越界,应当立刻停止程序。

Rust 没有异常,而使用 Result<T, E> 类型,以及 panic! 宏,在遇到不可恢复的错误时执行。

不可恢复错误

如果程序遇到了不可恢复的错误,可以调用 panic! 宏来退出:

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

默认情况下,将会有类似这样的报错:

thread 'main' panicked at 'crash and burn', src/bin/panic.rs:10:3
stack backtrace:
   0: _rust_begin_unwind
   1: core::panicking::panic_fmt
   2: _09_01_panic::main
             at ./src/bin/panic.rs:10:3
   3: core::ops::function::FnOnce::call_once
             at /private/tmp/rust-20220618-83111-1qsludx/rustc-1.61.0-src/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

在 panic 时,程序默认会开始展开(unwinding)并回溯调用栈并清理,这个过程有很多工作。另一种选择是直接终止(abort),直接让操作系统来清理,没有错误提示。可以这样修改配置来更改为 abort 行为:

[profile.release]
panic = 'abort'

终止不需要调用栈元数据,所以二进制包会更小。(更多减少大小的技巧可以参见 min-sized-rust

此外,可以设置 RUST_BACKTRACE 环境变量为 1 获取详细日志,设为 full 获取全日志。在 --release 情况下,也不会有 debug info。

可恢复错误

大部分错误没有严重到影响程序运行。可以使用 Result 类型处理错误:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

例如打开一个文件,就可能会失败:

use std::fs::File;

fn main() {
  let f = File::open("Hello.txt");
}

open 方法返回的正是 Result,具体类型是 Result<File, std::io::Error>

此时我们可以通过 match 处理不同情况:

fn main() {
  let f = File::open("hello.txt");

  let f = match f {
    Ok(file) => file,
    Err(error) => panic!("Problem opening the file: {:?}", error),
  };
}

更进一步,我们可以为不同的错误采取不同的处理方法:

fn main() {
  let f = File::open("hello.txt");

  let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
      ErrorKind::NotFound => match File::create("hello.txt") {
        Ok(fc) => fc,
        Err(e) => panic!("Problem creating the file: {:?}", e),
      },
      other_error => {
        panic!("Problem opening the file: {:?}", other_error)
      }
    },
  };
}

或者,使用闭包完成相同的逻辑(详细的介绍在以后进行):

fn main() {
  let f = File::open("hello.txt").unwrap_or_else(|error| {
    if error.kind() == ErrorKind::NotFound {
      File::create("hello.txt").unwrap_or_else(|error| {
        panic!("Problem creating the file: {:?}", error);
      })
    } else {
      panic!("Problem opening the file: {:?}", error);
    }
  });
}

传播错误

很多时候我们希望调用者处理该错误,所以我们可以将其向上传播(propagating)。

可以这样做:

fn read_username_from_file() -> Result<String, io::Error> {
  let f = File::open("hello.txt");

  let mut f = match f {
    Ok(file) => file,
    Err(e) => return Err(e),
  };

  let mut s = String::new();

  match f.read_to_string(&mut s) {
    Ok(_) => Ok(s),
    Err(e) => Err(e),
  }
}

我们还可以使用 ? 运算符简写错误传递:

fn read_username_from_file() -> Result<String, io::Error> {
  let mut f = File::open("hello.txt")?;
  let mut s = String::new();
  f.read_to_string(&mut s)?;
  Ok(s)
}

? 操作服允许链式调用:

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

请注意,? 对象相当于提前返回。所以返回值类型必须为 Result 或其他实现了 FromResidual 的类型。

我们也可以将 ? 用于 Option

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

main 函数中,也允许将 Result 作为返回值:

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
  let f = File::open("hello.txt")?;

  Ok(())
}

Box<dyn Error> 类型是一个 trait 对象(trait object),将在后续详细讲解。

当返回 Ok 时,将会以 0 值退出。若返回 Err 则会以非 0 值退出。

以上,就是错误处理相关的内容了。

comments powered by Disqus