Rust 入门学习笔记(四):基础错误处理

本文属于我的 Rust 学习笔记 系列。

Rust 入门学习笔记以实际例子为主,讲解部分不是从零开始的,所以不建议纯萌新观看,读者最好拥有任意一种面向对象语言的基础,然后自己多多少少看过 Rust 的基本语法,刷过一点 rustlings

来源:原子之音。当然也包含个人的一些补充。 视频 代码

Rust 进阶学习笔记以及实战的来源则五花八门,将会标注在下一行⬇️。

常见错误

错误

Rust 中的错误分为可恢复和不可恢复。

  • 可恢复:有返回类型,可以为 Result 或 Option
  • 不可恢复:panic! 终止当前线程

Result

Result 是一个枚举类型,通常用于表示函数执行的结果。

pub enum Result<T, E> {
    Ok(T), // 成功的结果
    Err(E), // 出现了错误
}

Option

Option 也是一个枚举类型,通常用于表示一个可能为空的值。

pub enum Option<T> {
    None, //    Some(T), // 其他返回值
}

panic 宏

当程序遇到无法继续执行的错误时,可以使用panic!宏来引发“恐慌”,导致程序立即终止并显示一条错误信息。

通常用于程序不能继续处理的情况下。


例子

fn divide(a: i32, b: i32) -> Result<f64, String> {
    if b == 0 {
        // 一般都要实现 std:error:Error 特质,否则将难以串联业务逻辑。
        // 这里是为了方便才用的 String
        return Err(String::from("cannot be zero"));
    }
    let a = a as f64;
    let b = b as f64;
    Ok(a / b)
}

fn find_element(array: &[i32], target: i32) -> Option<usize> {
    for (index, element) in array.iter().enumerate() {
        if (*element) == target {
            return Some(index);
        }
    }
    None
}

fn main() {
    // result
    match divide(1, 2) {
        Ok(number) => println!("{}", number),
        Err(err) => println!("{}", err),
    }

    match divide(1, 0) {
        Ok(number) => println!("{}", number),
        Err(err) => println!("{}", err),
    }

    // option
    let arr = [1, 2, 3, 4, 5];
    match find_element(&arr, 4) {
        Some(index) => println!("found in {}", index),
        None => println!("None"),
    }
    match find_element(&arr, 7) {
        Some(index) => println!("found in {}", index),
        None => println!("None"),
    }
    // panic
    let vec = vec![1, 2, 3, 4, 5]; // array 在编译时确定长度,而 vec 则没有,如用 array 出现的将是编译错误而非运行时
    vec[43];
    // 会输出 process didn't exit successfully
    // 使用 `RUST_BACKTRACE=1` 环境变量能够打印更详细的栈
}

错误结果处理

unwrap()

unwrap并不是一个安全的方法。它是ResultOption类型提供的方法之一,可用于获取OKSome的值,如果是ErrNone会引发panic

一般只用于确定不会出现错误或错误不需要处理的情况。

? 运算符

?用于简化ResultOption类型的错误传播,表示有预期之外的结果出现。它只能用于返回ResultOption的函数中,并且在函数内部也可以用于获取OKSome的值,如果是ErrNone则会立即提前返回。


例子

use std::num::ParseIntError;

fn find_first_even(numbers: Vec<i32>) -> Option<i32> {
    // 用了 ? 之后,在出现 None 或者 Err 时会立即返回
    let first_even = numbers.iter().find(|&num| num % 2 == 0)?;
    print!("Option");
    // find 的返回值是一个 Option<&i32> 类型,需要解引用
    Some(*first_even)
}

// 传递错误的处理模式,意思是可以把错误作为一种选择传到外部
fn parse_numbers(input: &str) -> Result<i32, ParseIntError> {
    let val = input.parse::<i32>()?;
    Ok(val)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result_ok: Result<i32, &str> = Ok(32);
    let value = result_ok.unwrap();
    println!("{}", value); // 32
    // let result_ok: Result<i32, &str> = Err("ff");
    // let value = result_ok.unwrap();
    // println!("{}", value); // panic
    let result_ok: Result<i32, &str> = Ok(32);
    // main 函数中想要使用 ?,必须修改返回值为 Result
    let value = result_ok?;
    println!("{}", value); // 32

    let numbers = vec![1, 3, 5];
    match find_first_even(numbers) {
        Some(number) => println!("first even {}", number),
        None => println!("no such number"), // 由于 ? 立即返回,此时并不会输出 find_first_even 里打印的 `Option`
    }

    match parse_numbers("d") {
        Ok(i) => println!("parsed {}", i),
        Err(err) => println!("failed to parse: {}", err), // 会报错`invalid digit found in string`,该信息来自 ParseIntError
    }

    Ok(())
}

自定义错误类型

  1. 定义错误类型结构体。需要先创建一个结构体来表示错误类型,其中通常会包含一些字段来描述详细错误信息。
  2. 实现 Display 特质。std::fmt::Display特质可以定义如何展示错误信息,方便使错误能够以人类可读的方式打印出来。
  3. 实现 Error 特质。std::error::Error特质用于满足 Rust 错误处理机制的要求,以便把错误从内层函数抛出到外层。

例子

#[derive(Debug)]
struct MyError {
    // 不要用字面量,否则要自己定义生命周期
    detail: String,
}

// 这是实现特质的语法
impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Custom Error: {}", self.detail)
    }
}

// 可以不重写 description,有默认的
impl std::error::Error for MyError {
    // 注意 description 现在已 deprecated,官方建议使用 Display 或 to_string
    fn description(&self) -> &str {
        &self.detail
    }
    // 字符串引用会自动被转成字符串字面量 &String => &str
}

fn func() -> Result<(), MyError> {
    Err(MyError {
        detail: "CustomError".to_owned(),
    })
    // Ok(())
}

fn main() -> Result<(), MyError> {
    match func() {
        Ok(_) => println!("func ok"),
        Err(err) => println!("Error: {}", err),
    }
    func()?;
    println!("oo"); // 错误时不会打印,如果想要完成传递,main 函数必须返回 Result<(), Box<dyn std::error::Error>>
    Ok(())
}

📝 系列导航