核心特性

第10课:错误处理Result

🎯 本课目标

掌握Rust的错误处理哲学、Result类型、?操作符、自定义错误类型和thiserror/anyhow库。

1. Rust错误处理哲学

Rust没有异常!错误处理分为两类:

编译器强制你处理Result,不会遗漏错误!

2. Result类型

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

use std::fs;
use std::io;

fn read_file(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn main() {
    match read_file("hello.txt") {
        Ok(content) => println!("内容: {}", content),
        Err(e) => println!("错误: {}", e),
    }
}

✅ 编译验证通过

3. ?操作符

?是Rust错误传播的语法糖——如果是Ok则解包,Err则提前返回:

use std::fs::File;
use std::io::{self, Read};

// 不用?的写法
fn read_username_old() -> Result<String, io::Error> {
    let f = File::open("username.txt");
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

// 用?的写法——简洁得多
fn read_username() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("username.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

// 更简洁的写法
fn read_username_v3() -> Result<String, io::Error> {
    std::fs::read_to_string("username.txt")
}

✅ 编译验证通过

4. 自定义错误类型

use std::fmt;

enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError,
    NotFound(String),
}

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

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

impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self { AppError::Parse(e) }
}

5. Result常用方法

fn main() {
    let ok: Result<i32, &str> = Ok(42);
    let err: Result<i32, &str> = Err("failed");

    // map / map_err
    let r = ok.map(|x| x * 2);             // Ok(84)
    let r2 = err.map_err(|e| e.to_uppercase()); // Err("FAILED")

    // and_then
    let r3 = ok.and_then(|x| if x > 0 { Ok(x) } else { Err("neg") });

    // unwrap_or / unwrap_or_else / unwrap_or_default
    let v = err.unwrap_or(0);
    let v2 = err.unwrap_or_else(|e| { println!("{}",e); 0 });
    let v3: i32 = err.unwrap_or_default();

    // is_ok / is_err
    println!("ok? {} err? {}", ok.is_ok(), err.is_err());
}

✅ 编译验证通过

6. panic!与不可恢复错误

fn main() {
    // panic! — 不可恢复,程序崩溃
    // panic!("出大事了!");

    // unwrap — None/Err时panic
    let v: Option<i32> = None;
    // v.unwrap();  // panic!

    // expect — 带消息的unwrap
    // v.expect("值不应该为None");

    // panic时可以设置是否展开栈
    // Cargo.toml: [profile.release] panic = "abort"
}

📝 练习题

练习1:文件处理链

用?实现:读文件→解析整数→计算平方→写入结果。

练习2:自定义错误

为计算器实现自定义错误类型,支持除零、溢出、语法错误。

练习3:Result与Option互转

练习ok_orokerrtranspose方法。

🏆

成就解锁:容错大师

掌握Rust错误处理——编译器保证不遗漏错误


📚 扩展阅读

以下资源帮助你深入学习本课主题:

🔑 关键术语回顾

本课涉及的核心概念,确保你理解每一个:

术语说明
所有权Rust内存管理的核心机制,每个值有唯一所有者
借用通过引用访问数据,不获取所有权
生命周期引用有效的范围,编译期分析工具
traitRust的接口/抽象机制,类似其他语言的接口
泛型参数化类型,零成本抽象
模式匹配强大的数据解构和分支机制
零成本抽象高层抽象不引入运行时开销
fearless concurrency编译器保证线程安全

💬 学习建议

  1. 动手编码 — 每个代码示例都在本地运行一遍
  2. 修改实验 — 故意改错代码,看编译器报什么错
  3. 完成练习 — 每课的练习题是巩固知识的关键
  4. 阅读源码 — 看标准库和优秀开源项目的实现
  5. 写项目 — 真正掌握需要构建真实项目

🤔 常见问题

Q: Rust学习曲线真的很陡吗?

A: 前期确实需要适应所有权和借用检查器,但一旦理解了,这些概念会让你的代码更可靠。大多数人2-4周就能上手。

Q: Rust适合什么项目?

A: 系统编程、Web服务、CLI工具、嵌入式、WASM、网络服务、数据库等。基本上需要性能和安全的地方都适合。

Q: 遇到编译错误怎么办?

A: Rust编译器的错误信息非常友好!仔细阅读,通常会指出问题所在和修复建议。也可以用cargo clippy获取更多提示。