Skip to content

错误处理

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

  • 不可恢复错误: 使用 panic! 宏,程序遇到不可恢复的错误时停止执行
  • 可恢复错误: 使用 Result<T, E> 类型处理,允许函数返回错误但继续运行

用 panic! 处理不可恢复的错误

panic! 宏用于处理程序无法继续运行的情况.当触发 panic 时,程序会停止执行.

panic 的行为控制

  • 出现 panic 时,程序默认会开始展开(unwinding),逐层清理资源
  • Cargo.toml[profile] 部分增加 panic = 'abort',可以由展开切换为终止,程序立即退出,可以使最终生成的二进制文件变小

配置示例

在 release 模式下由展开切换为终止

toml
[profile.release]
panic = 'abort'

调试 panic 信息

设置 RUST_BACKTRACE 环境变量查看详细的错误堆栈信息:

  • RUST_BACKTRACE=1: 显示堆栈跟踪
  • RUST_BACKTRACE=full: 显示完整堆栈信息,包括标准库调用
bash
RUST_BACKTRACE=1 cargo run

panic! 宏的使用

rust
fn main() {
    panic!("程序崩溃了!这是一个不可恢复的错误");
}

用 Result 处理可恢复的错误

Result<T, E>

  • 表示要么成功,要么失败,可以携带信息
  • Option<T> 类似,只有 2 个变体: Ok(T)Err(E)
rust
enum Result<T, E> {
    Ok(T),
    Err(E),
}

最简单的错误处理方式

对于简单场景,直接使用 matchif let 处理:

rust
use std::fs::File;

fn main() {
    match File::open("hello.txt") {
        Ok(file) => println!("文件打开成功"),
        Err(error) => println!("打开失败: {}", error),
    }
}

失败时 panic 的快捷方式: unwrap 和 expect

  • 只能使用在 ResultOption 类型上
  • unwrap(): 是 Err<E> 时直接 panic
  • expect(&str): 可以带自定义错误信息,是 Err<E> 时直接 panic
rust
use std::fs::File;

fn main() {
    let get_file = File::open("hello.txt");
    dbg!(get_file.unwrap());
    dbg!(get_file.expect("找不到文件"));
}

传播错误的快捷方式: ? 运算符

  • 当你在函数中使用 ? 时,是在说: "如果这里出错了,我不想在这个函数里处理它,请把它传给调用我的那个人"
  • 相当于"甩锅",把错误传递给上一层调用者
  • 只能用于返回值是 ResultOption 的函数
rust
use std::fs::File;
use std::io::{self, Read};

// 最底层的函数 B: 负责读文件
fn read_config() -> Result<String, io::Error> {
    // 如果文件不存在,? 会立即让函数返回 Err,不再执行后面的代码
    let mut f = File::open("config.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

// 中间层函数 A: 直接把 B 的错误传给上层
fn process_config() -> Result<usize, io::Error> {
    let content = read_config()?; // 这里也用 ?,继续往上传
    Ok(content.len())
}

// 最高层 main: 最终在这里处理错误
fn main() {
    match process_config() {
        Ok(size) => println!("文件大小: {}", size),
        Err(e) => println!("报错了!错误原因是: {}", e), // 最终在这里处理
    }
}

? 运算符自动类型转换

  • ? 运算符最强大之处在于甩锅时会自动调用 From::from 进行错误类型转换,这解决了多种错误类型的统一返回问题.
  • 这解决了一个痛点: 如果一个函数里可能发生多种类型的错误,返回值该写哪一个的问题
  • 简单理解就是 Result 的错误类型返回必须保持一致,或者被包含,如果错误范围足够大就可以返回任意错误类型(Box<dyn std::error::Error>)

只能用于函数返回值是 Result or Option 的函数里面

? 实际上是一个解包 (Unwrap) 操作:

  • 如果是 Some(v)/Ok(v),? 的表达式结果就是 v.
  • 如果是 None/Err(E),它会立即执行 return None/Err(E).

和TS不一样的是:使用?.如果值不存在并不会影响下一步操作,而rust直接停止(return)执行下一步

使用 Trait Object 进行类型转换

  • 使用 Box<dyn std::error::Error>,最简单,生产常用,它可以接收任何实现了 Error 特性的错误类型.
rust
use std::error::Error;
use std::fs::File;
use std::io::Read;

fn get_number_from_file() -> Result<i32, Box<dyn Error>> {
    let mut s = String::new();
    // ? 发现 io::Error 不等于 Box<dyn Error>
    // 于是自动调用了 io::Error::into() 将其转换并装箱
    File::open("num.txt")?.read_to_string(&mut s)?;
    // ? 同样将 ParseIntError 自动转换为了 Box<dyn Error>
    let num: i32 = s.trim().parse()?;
    Ok(num)
}

自定义错误枚举

手动为错误类型实现 From trait,使 ? 能精准转换:

rust
use std::io;
use std::num::ParseIntError;

enum MyError {
    Io(io::Error),
    Parse(ParseIntError),
}
// 手动实现从 io::Error 到 MyError 的转换
impl From<io::Error> for MyError {
    fn from(err: io::Error) -> MyError {
        MyError::Io(err)
    }
}

impl From<ParseIntError> for MyError {
    fn from(err: ParseIntError) -> MyError {
        MyError::Parse(err)
    }
}

// 这样在函数里就能统一返回 Result<T, MyError>,? 运算符会自动干活
fn get_number() -> Result<i32, MyError> {
    let mut s = String::new();
    File::open("num.txt")?.read_to_string(&mut s)?;
    let num: i32 = s.trim().parse()?;
    Ok(num)
}

Result<T, E> 的常用方法

方法作用
is_ok()判断是否是 Ok 变体,返回 truefalse
is_err()判断是否是 Err 变体,返回 truefalse
ok()Ok(v) 转换为 Some(v), 将 Err(e) 转换为 None
err()Err(e) 转换为 Some(e),将 Ok(v) 转换为 None
unwrap()取出 Ok 中的值;若是 Err 则 panic.
unwrap_or(def)有值取值;若是 Err 则返回 def.
unwrap_or_else(f)若是 Err(e),执行闭包 f(e);根据错误生成默认值.
expect(msg)unwrap,但 panic 时显示自定义 msg 和错误内容.

Option 和 Result 的对比

错误处理

工具Option<T>Result<T, E>
unwrap()取出 Some 中的值;若是 None 则 panic.取出 Ok 中的值;若是 Err 则 panic 并打印错误.
unwrap_or(def)有值取值;若是 None 则返回 def.有值取值;若是 Err 则返回 def.
unwrap_or_else(f)若是 None,执行闭包 f() 并返回其结果(延迟求值).若是 Err(e),执行闭包 f(e);根据错误生成默认值.
expect(msg)unwrap,但 panic 时显示自定义 msg.unwrap,但 panic 时显示 msg 和错误内容.
? 运算符Some 则解包;None 则函数提前返回 None.Ok 则解包;Err 则函数提前返回 Err(E).

? 运算符的类型匹配要求

  • 函数返回 Option 时,只能对 Option 类型使用 ?
  • 函数返回 Result 时,只能对 Result 类型使用 ?
  • 混合使用时需要先转换: option_val.ok_or("错误信息")?

总结: unwrap/expect 用于报错;unwrap_or 用于提供默认值;? 用于错误传播.

Option 和 Result 互转

  • 核心逻辑很简单: None 对应 Err,Some 对应 Ok
  • 由于 Result 多了一个"错误信息",所以转换时通常需要你补全或丢弃那个错误信息

Option → Result (最常用)

当你有一个可选值,但如果它不存在就想当作一个错误上报时使用.

  • ok_or(err): 如果是 None,则返回 Err(err);如果是 Some(v),则返回 Ok(v)
  • ok_or_else(f): 只有在 None 时才执行闭包 f() 计算错误消息,性能更好(延迟求值)
rust
let opt: Option<i32> = None;
// 转换成 Result
let res = opt.ok_or("错误: 值不存在");
// 结果: Err("错误: 值不存在")
let res_lazy = opt.ok_or_else(|| format!("{} 找不到", "ID"));
// 结果: Err("ID 找不到")

Result → Option

当你只关心成功的值,想忽略具体的错误原因时使用

  • ok(): 把 Ok(v) 变成 Some(v),把 Err(e) 直接丢弃变成 None
  • err(): 反过来,把错误提取出来变成 Some(e),成功的值反而变 None
rust
let res: Result<i32, &str> = Err("IO Error");
// 只想要成功的值
let opt = res.ok();
// 结果: None (错误被丢弃了)
// 只想要错误信息
let err_opt = res.err();
// 结果: Some("IO Error")

综合例子

rust
use std::fs::File;

fn main() {
    let x = File::open("./src/a.rs").expect("没有找到文件");
    // dbg!(&x);

    let y = add();
    dbg!(&y);
    if let Option::Some(ok) = y {
        dbg!(ok);
    }
}

fn add() -> Option<File> {
    let x = Some(1 + 1)?; // 函数期望返回Option,这里也是Option类型,可以使用
    Some(x);
    let y = File::open("./src/a.rs").ok()?; // 函数期望返回Option,使用ok()函数将Result转换为Option类型
    Some(y) // 返回 Option<File>类型
}

基于 MIT 协议发布