出错了怎么办:错误处理

原理

Rust 将错误分为两类:

panic! — 不可恢复,调用 std::panic! 宏触发栈展开(unwinding)或直接终止(abort)。展开时沿调用栈逐帧调用 drop,由 panic_handlerset_hook 控制。可在 Cargo.toml 中设置 panic = "abort" 跳过展开,减小二进制体积。

Result<T, E> — 可恢复,定义为带两个泛型参数的枚举:

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

? 运算符在编译时展开为 match + return Err,其中通过 From trait 做错误类型自动转换。每个 ? 相当于一个提前返回(early return),保证错误不丢失。

unwrap / expect 本质是 match result { Ok(v) => v, Err(e) => panic!(...)}

Option<T>Result<T, E> 的关系:None 可视为 Err(()) 但不携带额外信息。两者均可使用 ? 传播(需函数返回对应类型)。

C: 错误处理


语法

panic!

panic!("致命错误");
// 仅用于不可恢复的情况或原型阶段

Result 基本使用

use std::fs::File;
 
let f = match File::open("data.txt") {
    Ok(file) => file,
    Err(e) => {
        eprintln!("打开失败:{}", e);
        return;
    }
};

unwrap / expect

let f = File::open("data.txt").unwrap();           // Err 时 panic
let f = File::open("data.txt").expect("必须存在");  // 自定义消息

? 运算符 — 传播错误

use std::fs;
use std::io::{self, Read};
 
fn read_file() -> Result<String, io::Error> {
    let mut f = File::open("data.txt")?;   // Err 时提前返回
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = fs::read_to_string("data.txt")?;
    println!("{}", content);
    Ok(())
}

错误类型匹配

use std::io::ErrorKind;
 
match File::open("data.txt") {
    Ok(f) => f,
    Err(e) => match e.kind() {
        ErrorKind::NotFound => File::create("data.txt").unwrap(),
        _ => panic!("{}", e),
    },
};

unwrap_or / unwrap_or_else

let x: Result<i32, &str> = Err("fail");
x.unwrap_or(0);              // → 0
 
File::open("c.toml").unwrap_or_else(|e| {
    if e.kind() == ErrorKind::NotFound {
        File::create("c.toml").unwrap()
    } else { panic!("{}", e); }
});

错误处理策略

场景方式
原型/示例unwrap() / expect()
库代码Result<T, E> + ? 传播
致命错误panic!
应用层main 中用 match 最后处理

实践

力扣问题

力扣: 力扣数组计数题 — unwrap_or 处理缺失

let val = map.get(&num).copied().unwrap_or(0);

AI 自检

  1. ?Result 返回 Err 时,错误类型如何自动转换?From trait 的作用?
  2. panic = "abort" 和默认展开模式的区别是什么?对二进制大小有什么影响?