错误处理概述
> 理解 Rust 的错误处理哲学,掌握 Result 和 Option 类型的基本用法,对比其他语言的错误处理机制。
为什么错误处理很重要?
概念名称: Rust 使用 Result<T, E> 和 Option<T> 类型将错误作为值处理,而非异常。
语法结构:
┌──────────────────────────────────────┐
│ enum Result<T, E> { │
│ Ok(T), // 成功,包含值 │
│ Err(E), // 失败,包含错误 │
│ } │
│ │
│ enum Option<T> { │
│ Some(T), // 有值 │
│ None, // 无值 │
│ } │
│ │
│ 处理模式: │
│ match result { │
│ Ok(v) => ... │
│ Err(e) => ... │
│ } │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("除数不能为零".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("结果:{}", result),
Err(e) => println!("错误:{}", e),
}
}现实世界的程序总会出错
rust
▶ Run// 可能失败的操作
std::fs::File::open("config.txt"); // 文件可能不存在
std::net::TcpStream::connect("localhost:8080"); // 服务器可能离线
"123".parse::<i32>(); // 字符串可能不是有效数字
vec![1, 2, 3][10]; // 索引可能越界错误处理的目标
┌─────────────────────────────────────────────────────┐
│ 良好的错误处理 │
├─────────────────────────────────────────────────────┤
│ │
│ 1. 程序健壮性 │
│ • 遇到错误时不会崩溃 │
│ • 能够优雅地降级或恢复 │
│ │
│ 2. 用户友好 │
│ • 提供清晰的错误信息 │
│ • 指导用户如何解决问题 │
│ │
│ 3. 易于调试 │
│ • 记录足够的上下文信息 │
│ • 追踪错误来源 │
│ │
│ 4. 类型安全 │
│ • 编译器强制处理错误 │
│ • 不会遗漏错误情况 │
│ │
└─────────────────────────────────────────────────────┘为什么用它?
rust
▶ Run// 没有 Result:错误被忽略,程序崩溃
let file = std::fs::File::open("config.txt").unwrap();
// 如果文件不存在,直接 panic!
// 有 Result:错误被显式处理
match std::fs::File::open("config.txt") {
Ok(file) => println!("文件打开成功"),
Err(e) => println!("文件打开失败:{}", e),
}
// 使用 ? 操作符简化
fn read_config() -> Result<String, std::io::Error> {
let content = std::fs::read_to_string("config.txt")?;
Ok(content)
}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
Result<T, E> | 成功/失败的结果类型 | 将错误作为值,强制调用者处理 |
Ok(T) | 成功分支 | 包含有效值 |
Err(E) | 失败分支 | 包含错误信息 |
? 操作符 | 错误传播语法糖 | 自动返回 Err,简化代码 |
.unwrap() | 提取值或 panic | 仅用于测试或确定不会失败时 |
小结
- Rust 使用
Result<T, E>和Option<T>将错误作为值处理 - 错误处理的目标:健壮性、用户友好、易于调试、类型安全
?操作符自动传播错误,大幅简化代码- 编译器强制处理错误,不会遗漏错误情况
练习题
详见:练习题