Skip to content

简单的错误处理

Rust 有一套独特的处理异常情况的机制,它把错误分为两种:可恢复错误和不可恢复错误。

不可恢复错误指的是在编程中的逻辑错误导致的。

可恢复错误指的是程序或者命令执行失败,通过等待、重载等方式可以解决,意料之内的行为。

在 Rust 中有多种处理错误的方式,它们多少有些区别,使用场景也不尽相同。

  • panic 主要用于测试,以及处理不可恢复的错误。在原型开发中这很有用,比如用来测试还没有实现的函数。
  • Option 类型是为了值是可选的、或者缺少值并不是错误的情况准备的。比如说寻找父目录时,/ 和 C: 这样的目录就没有父目录,这应当并不是一个错误。
  • 当错误有可能发生,且应当由调用者处理时,使用 Result。

可恢复错误

Option

在标准库中有个叫做 Option<T> 的枚举类型,它表现为以下两个 “option”(选项)中的一个:

  • Some(T):找到一个属于 T 类型的元素
  • None:找不到相应元素

当你有一个可能存在,也可能不存在的值时,你就用Option。当一个值存在的时候就是Some(value),不存在的时候就是None。代码示例:

rust
fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 { // .len() gives the length of the vec.It must be at least 5.
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    println!("{:?}, {:?}", take_fifth(new_vec), take_fifth(bigger_vec));
}

Option 可以和 match 结合使用显示的进行处理:

rust
fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

或者使用 unwrap 隐式地处理。隐式处理要么返回 Some 内部的元素,要么就 panic。比如在下面打开文件的示例中,可以使用 unwrap 和 expect 简化这个过程。它的作用就是如果返回成功就将 Ok(T) 中的值取出,如果失败直接进行 panic 。

rust
use std::fs::File;

fn main() {
    let _f1 = File::open("hello_1.txt").unwrap();
    let _f2 = File::open("hello_2.txt").expect("Failed to open hello_2.txt");
}

使用 match 语句来解开 Option,但使用 ? 运算符通常会更容易:

rust
fn next_birthday(current_age: Option<u8>) -> Option<String> {
    // 如果 `current_age` 是 `None`,这将返回 `None`。
    // 如果 `current_age` 是 `Some`,内部的 `u8` 将赋值给 `next_age`。
    let next_age: u8 = current_age?;
    Some(format!("Next year I will be {}", next_age))
}

Result

大部分错误并没有严重到需要程序完全停止执行。有时,一个程序可能会遇到文件打开失败、连接服务器失败等错误,这时候直接程序崩溃不是正确的做法。

更通用的做法是是利用 Result 枚举类作返回值来进行异常表达,然后对一场进行处理。它定义有如下两个成员,OkErrT表示成功返回的成员的类型,E表示失败时返回的错误的类型:

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

针对返回的Result我们可以用模式匹配来对返回的成功或者失败进行处理,例如当打开一个文件时,如果打开成功那么将Ok(file)中的file赋值到t中。如果失败,将返回一个error赋值到t中。后续的处理中,我们可以根据返回值进行操作。

rust
use std::fs::File;

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

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

此外,在实际的应用中,我们可以匹配不同的错误,然后对应不同的处理方式:

rust
use std::fs::File;
use std::io::ErrorKind;

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)
            }
        },
    };
}

上面代码在匹配出 error 后,又对 error 进行了详细的匹配解析,最终结果:

  • 如果是文件不存在错误 ErrorKind::NotFound,就创建文件,这里创建文件File::create 也是返回 Result,因此继续用 match 对其结果进行处理:创建成功,将新的文件句柄赋值给 f,如果失败,则 panic
  • 剩下的错误,一律 panic

不可恢复错误

panic

最简单的错误处理机制是panic。在以下的代码中它打印一条错误消息,开始展开堆栈,并且通常退出程序。示例:

rust
fn main() {
    panic!("程序崩溃");
}

在程序中发生panic错误的话可以通过设置 RUST_BACKTRACE 环境变量,然后运行程序来获得一个 backtracebacktrace 是一个执行到目前报错位置所有被调用的函数的列表。

例如在命令行中运行命令:RUST_BACKTRACE=1 cargo run

assertion

断言表现为布尔表达式,在一些场景下,使用断言判断当前的计算结果和预期是否一致。Rust 提供了一些非常好用的断言:

  • assert!, assert_eq!, assert_ne!, 它们会在所有模式下运行
rust
fn main() {
    let a = 3;
    let b = 1 + 2;
    
    // 判断两者是否相等,如果不相等,会直接panic!
    assert_eq!(a, b);
    // 判断两者是否不相等,如果相等则panic!
    assert_ne!(a, b);
    // 判断传入的布尔表达式是否为 true,如果是false则panic!
    let x = true;
	assert!(x);
}
  • debug_assert!, debug_assert_eq!, debug_assert_ne!, 它们只会在 Debug 模式下运行,如果在release模式下将不会执行。
rust
fn main() {
    let a = 3;
    let b = 1 + 3;
    
    // 判断两者是否相等
    debug_assert_eq!(a, b);
    // 判断两者是否不相等,如果相等则panic!
    debug_assert_ne!(a, b);
    // 判断传入的布尔表达式是否为 true,如果是false则panic!
    let x = true;
	debug_assert!(x);
}