Alex

Rust 错误处理

  • Rust
  • 错误处理

在 Rust 中,错误处理是一门艺术。它不像 Java 或 Python 那样使用“异常(Exceptions)”,而是通过强大的类型系统来强制你面对现实:程序总会出点意外。

Rust 将错误分为两大类:不可恢复错误可恢复错误


0) 不可恢复错误:panic!

当程序遇到无法处理的灾难性问题(如数组越界、除以零)时,会触发 panic!。此时程序会打印错误信息、展开(unwind)并清理栈数据,最后退出。

fn main() {
    panic!("程序在这里原地爆炸了");
}
  • 适用场景:逻辑漏洞、无法恢复的系统状态、示例代码或测试。

1) 可恢复错误:Result<T, E>

这是 Rust 错误处理的精髓。它是一个枚举,定义如下:

enum Result<T, E> {
    Ok(T),  // 成功时返回的数据
    Err(E), // 失败时返回的错误信息
}

如何处理 Result

A. 模式匹配 (Match)

最原始、最彻底的处理方式。

use std::fs::File;
 
let f = File::open("hello.txt");
let file = match f {
    Ok(file) => file,
    Err(error) => panic!("打开文件失败: {:?}", error),
};

B. 快捷处理:unwrapexpect

如果你确定(或不在乎)结果一定是 Ok,可以使用这两个方法。

  • unwrap():如果是 Err,直接 panic
  • expect("自定义错误消息"):如果是 Err,带着你的消息 panic

C. 给默认值:unwrap_orunwrap_or_else

当你不想 panic,而是希望在失败/为空时回退到一个默认值,可以用:

  • unwrap_or(default):直接给一个默认值(默认值会被立即求值
  • unwrap_or_else(|| default):给一个闭包,只有在需要默认值时才计算(惰性求值

这两个方法在 Option<T>Result<T, E> 上都能用:

// Option<T>
let port: u16 = std::env::var("PORT")
    .ok()
    .and_then(|s| s.parse().ok())
    .unwrap_or(8080);
 
let port2: u16 = std::env::var("PORT")
    .ok()
    .and_then(|s| s.parse().ok())
    .unwrap_or_else(|| {
        // 只有在没有 PORT 时才会执行
        8080
    });
 
// Result<T, E>
let n: i32 = "123".parse::<i32>().unwrap_or(0);
let n2: i32 = "123".parse::<i32>().unwrap_or_else(|_err| 0);

一般经验是:

  • 默认值很便宜、也不依赖错误信息:优先 unwrap_or
  • 默认值计算较贵、或你想基于 Err 做日志/分支:用 unwrap_or_else

2) 错误传播的艺术:? 运算符

这是 Rust 开发中最常用的“语法糖”。它让错误处理变得极其简洁。

如果函数返回 Result,你可以在调用其他返回 Result 的方法后加一个 ?

  • 如果结果是 Ok,它会解包出里面的值。
  • 如果结果是 Err,它会立即提前返回,把错误抛给调用者。
use std::fs::File;
use std::io::{self, Read};
 
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?; // 如果失败,直接返回 Err
    let mut s = String::new();
    f.read_to_string(&mut s)?; // 如果失败,直接返回 Err
    Ok(s)
}

3) Option<T>:处理“空值”

Rust 没有 null。当一个值可能存在也可能不存在时,使用 Option<T>

  • Some(T):找到了值。
  • None:啥也没有。

它同样支持 matchunwrap 以及 ? 运算符。


4) 进阶:自定义错误类型

在大型项目中,通常会定义自己的错误类型,或者使用社区流行的库来简化逻辑:

  • thiserror:适合开发库(Library),帮助你快速定义规范的错误类型。
  • anyhow:适合开发应用(Application),它提供了一个万能的错误类型 anyhow::Result,让你随心所欲地抛出各种错误。

💡 核心建议

  1. 多用 Result,少用 panic!
  2. 善用 ? 运算符,保持代码整洁。
  3. 不要滥用 unwrap,除非是在写 Demo 或测试代码,否则它就是程序崩溃的隐患。