错误处理

Rust 将错误分为两类:可恢复错误Result<T, E>)和不可恢复错误(panic!)。

panic! 宏

遇到不可恢复的错误时,程序会 panic:

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

何时使用 panic

  • 示例代码、原型
  • 不可能发生的情况(逻辑错误)
  • 库代码中的契约违反

Result 枚举

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

use std::fs::File;

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

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("打开文件失败: {:?}", error);
        }
    };
}

匹配不同错误

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!("创建文件失败: {:?}", e),
            },
            other_error => panic!("打开文件失败: {:?}", other_error),
        },
    };
}

unwrap 和 expect

use std::fs::File;

fn main() {
    // unwrap:Ok 返回值,Err 则 panic
    let f = File::open("hello.txt").unwrap();

    // expect:可以自定义 panic 消息
    let f = File::open("hello.txt")
        .expect("无法打开 hello.txt");
}

传播错误

使用 ? 运算符:

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)?;
    Ok(s)
}

// 更简洁的链式调用
fn read_username_simplified() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}
Loading diagram...

? 只能用于返回 Result 的函数

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let f = File::open("hello.txt")?;  // ✅ 现在可以在 main 中使用 ?
    Ok(())
}

自定义错误类型

use std::fmt;

#[derive(Debug)]
enum MyError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::Io(e) => write!(f, "IO 错误: {}", e),
            MyError::Parse(e) => write!(f, "解析错误: {}", e),
        }
    }
}

impl std::error::Error for MyError {}

// 使用 From trait 自动转换
impl From<std::io::Error> for MyError {
    fn from(err: std::io::Error) -> MyError {
        MyError::Io(err)
    }
}

Option vs Result

// Option:表示可能没有值
fn find_user(id: u32) -> Option<User> {
    // ...
}

// Result:表示可能失败的操作
fn save_user(user: &User) -> Result<(), DatabaseError> {
    // ...
}

and_then 和 map

fn main() {
    let result: Result<i32, &str> = Ok(2);

    // map:转换 Ok 中的值
    let doubled = result.map(|x| x * 2);  // Ok(4)

    // and_then:链式调用可能失败的操作
    let result = Ok(2)
        .and_then(|x| Ok(x * 2))
        .and_then(|x| Ok(x + 1));  // Ok(5)
}

实践建议

✅ 推荐做法

// 1. 使用 ? 传播错误
fn process() -> Result<(), MyError> {
    let data = read_data()?;
    transform(data)?;
    Ok(())
}

// 2. 使用 expect 提供上下文
let config = load_config()
    .expect("配置文件必须存在且格式正确");

// 3. 库代码返回 Result,应用代码处理错误
pub fn parse_number(s: &str) -> Result<i32, ParseError> {
    s.parse().map_err(|e| ParseError::InvalidFormat(e))
}

❌ 避免做法

// ❌ 滥用 unwrap
let value = may_fail().unwrap();  // 可能导致 panic

// ❌ 忽略错误
let _ = may_fail();  // 错误被静默忽略

// ❌ 过度使用 panic
if x < 0 {
    panic!("x 不能为负");  // 应该使用 Result
}

小结

  • Result<T, E> 用于可恢复错误
  • panic! 用于不可恢复错误
  • ? 运算符简化错误传播
  • unwrapexpect 用于原型和测试
  • ✅ 自定义错误类型提高可读性
  • ✅ 库代码应返回 Result,让调用者处理