Skip to content

Commit 88e2207

Browse files
author
mudssky
committed
docs(cheatsheet): 添加Rust错误处理速查表文档
新增Rust错误处理速查表文档,涵盖Option/Result使用、thiserror和anyhow库的最佳实践,以及错误处理的核心概念和场景选择指南
1 parent 0ec27e2 commit 88e2207

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
2+
## Rust 错误处理速查表 (Cheatsheet)
3+
4+
### 核心理念
5+
6+
Rust 通过在类型系统中显式表达错误,强制开发者处理潜在的失败情况,从而构建出高可靠性的软件。核心思想是区分**可恢复的错误 (Recoverable Errors)****不可恢复的错误 (Unrecoverable Errors)**
7+
8+
| 类别 | 描述 | 主要工具 |
9+
| :--- | :--- | :--- |
10+
| **可恢复错误** | 预料之中的、可以被合理处理的错误(如文件未找到、网络中断)。 | `Result<T, E>` |
11+
| **不可恢复错误** | 意料之外的、代表程序缺陷的错误(如数组越界访问)。 | `panic!` |
12+
13+
---
14+
15+
### 1. Rust 内置错误处理机制
16+
17+
#### `Option<T>`:处理“值可能不存在”的情况
18+
19+
用于表示一个值可能是 `Some(value)``None`(空)。
20+
21+
* **定义**`enum Option<T> { Some(T), None }`
22+
* **常用方法**
23+
* `unwrap()`:获取 `Some` 里的值,但如果为 `None` 则会 `panic!`**慎用!**
24+
* `expect("message")`:与 `unwrap()` 类似,但在 `panic!` 时提供自定义消息。
25+
* `unwrap_or(default_value)`:如果为 `None`,则返回一个默认值。
26+
* `is_some()` / `is_none()`:检查是否包含值。
27+
* `map(fn)` / `and_then(fn)`:对内部的值进行操作。
28+
29+
```rust
30+
fn find_user(id: u32) -> Option<String> {
31+
if id == 1 { Some("Alice".to_string()) } else { None }
32+
}
33+
34+
let user = find_user(1).unwrap_or("Default User".to_string()); // "Alice"
35+
let user2 = find_user(2).unwrap_or("Default User".to_string()); // "Default User"
36+
```
37+
38+
#### `Result<T, E>`:处理“操作可能失败”的情况
39+
40+
用于表示一个操作的结果可能是 `Ok(value)`(成功)或 `Err(error)`(失败)。
41+
42+
* **定义**`enum Result<T, E> { Ok(T), Err(E) }`
43+
* **常用方法**
44+
* `unwrap()` / `expect("message")`:与 `Option` 类似,成功时返回值,失败时 `panic!`
45+
* `is_ok()` / `is_err()`:检查结果是成功还是失败。
46+
* `ok()`:将 `Result<T, E>` 转换为 `Option<T>`
47+
* `err()`:将 `Result<T, E>` 转换为 `Option<E>`
48+
* `map_err(fn)`:在不改变 `Ok` 值的情况下,转换 `Err` 中的错误类型。
49+
50+
```rust
51+
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
52+
s.parse::<i32>()
53+
}
54+
55+
match parse_number("123") {
56+
Ok(num) => println!("Success: {}", num), // Success: 123
57+
Err(e) => println!("Error: {}", e),
58+
}
59+
```
60+
61+
#### `?` 操作符:优雅地传播错误
62+
63+
`?` 是 Rust 错误处理的语法糖,用于简化错误传播。它只能用于返回 `Result``Option` 的函数。
64+
65+
* **工作原理**:如果 `Result``Ok(T)`,它会解包出 `T`;如果是 `Err(E)`,它会立即从当前函数返回 `Err(E)`
66+
* **前提**:当前函数的错误类型必须能通过 `From::from` trait 从 `?` 操作的错误类型转换而来。
67+
68+
```rust
69+
// 旧方法 (手动 match)
70+
fn read_username_from_file_old() -> Result<String, std::io::Error> {
71+
let mut f = match std::fs::File::open("username.txt") {
72+
Ok(file) => file,
73+
Err(e) => return Err(e),
74+
};
75+
let mut s = String::new();
76+
match f.read_to_string(&mut s) {
77+
Ok(_) => Ok(s),
78+
Err(e) => Err(e),
79+
}
80+
}
81+
82+
// 新方法 (使用 ?)
83+
use std::io::{self, Read};
84+
fn read_username_from_file_new() -> Result<String, io::Error> {
85+
let mut s = String::new();
86+
// 如果 open 失败,? 会立即返回 Err
87+
std::fs::File::open("username.txt")?.read_to_string(&mut s)?;
88+
Ok(s)
89+
}
90+
```
91+
92+
---
93+
94+
### 2. `thiserror`:为你的库创建专业的错误类型
95+
96+
**目标**:为库(library/crate)定义具体的、结构化的错误类型,方便库的使用者以编程方式处理它们。
97+
98+
**安装**`cargo add thiserror`
99+
100+
**用法**
101+
102+
1. 创建一个 `pub enum Error`
103+
2. 使用 `#[derive(Error, Debug)]`
104+
3.`#[error("...")]` 为每个变体定义错误信息。
105+
4.`#[from]` 将底层错误自动转换为你的错误类型。
106+
107+
**示例**
108+
109+
```rust
110+
// 在你的库代码中 (e.g., src/lib.rs)
111+
use thiserror::Error;
112+
113+
// 1. 定义一个通用的 Result 别名
114+
pub type Result<T> = std::result::Result<T, DataError>;
115+
116+
// 2. 定义你的自定义错误枚举
117+
#[derive(Error, Debug)]
118+
pub enum DataError {
119+
#[error("数据库连接失败")]
120+
DatabaseError(#[source] sqlx::Error), // `#[source]` 用于链接底层错误
121+
122+
#[error("I/O 错误: {0}")]
123+
IoError(#[from] std::io::Error), // `#[from]` 让 `?` 自动转换 io::Error
124+
125+
#[error("找不到记录 ID: {id}")]
126+
NotFound { id: String },
127+
128+
#[error("输入无效: {message}")]
129+
InvalidInput { message: String },
130+
}
131+
132+
// 3. 在函数中使用
133+
pub fn get_data(id: &str) -> Result<String> {
134+
if id.is_empty() {
135+
// 返回一个具体的错误变体
136+
return Err(DataError::InvalidInput { message: "ID 不能为空".into() });
137+
}
138+
// `?` 会自动将 std::io::Error 转换为 DataError::IoError
139+
let config = std::fs::read_to_string("config.txt")?;
140+
141+
// ... 其他逻辑 ...
142+
Err(DataError::NotFound { id: id.to_string() })
143+
}
144+
```
145+
146+
---
147+
148+
### 3. `anyhow`:简化应用程序的错误处理
149+
150+
**目标**:在应用程序(binary)中,当你不太关心具体的错误类型,只想轻松地传播错误并添加上下文时使用。
151+
152+
**安装**`cargo add anyhow`
153+
154+
**用法**
155+
156+
1. 在函数签名中使用 `anyhow::Result<T>`
157+
2. `?` 可以作用于任何实现了 `std::error::Error` 的错误类型。
158+
3. 使用 `.context("...")``.with_context(|| ...)` 方法在错误传播链上添加上下文信息。
159+
160+
**示例**
161+
162+
```rust
163+
// 在你的应用程序代码中 (e.g., src/main.rs)
164+
use anyhow::{Context, Result};
165+
166+
// my_library 是上面用 thiserror 编写的库
167+
use my_library::DataError;
168+
169+
fn main() -> Result<()> {
170+
// anyhow::Result 让我们可以在 main 函数中使用 ?
171+
let data = load_user_data("user123")
172+
.with_context(|| "无法加载用户数据")?;
173+
174+
println!("成功获取数据: {}", data);
175+
Ok(())
176+
}
177+
178+
fn load_user_data(id: &str) -> Result<String> {
179+
// 调用库函数,? 会自动将 DataError 包装进 anyhow::Error
180+
let data = my_library::get_data(id)
181+
.context("从 my_library 获取数据失败")?;
182+
183+
// ... 其他逻辑 ...
184+
Ok(data)
185+
}
186+
187+
// 当运行并出错时,anyhow 会打印出完整的错误链:
188+
// Error: 无法加载用户数据
189+
//
190+
// Caused by:
191+
// 0: 从 my_library 获取数据失败
192+
// 1: 找不到记录 ID: user123
193+
```
194+
195+
---
196+
197+
### 总结:何时使用什么?
198+
199+
| 场景 | 推荐工具 | 为什么? |
200+
| :--- | :--- | :--- |
201+
| **编写库 (Library)** | **`thiserror`** | 创建具体的、结构化的错误类型,让调用者可以 `match` 并从容处理。 |
202+
| **编写应用程序 (Application)** | **`anyhow`** | 简单!轻松包装任何错误,添加上下文,并打印出用户友好的报告。 |
203+
| **函数可能返回空值** | **`Option<T>`** | 这是“没有值”的语义,而不是“失败”。 |
204+
| **在函数间传播错误** | **`?` 操作符** | 保持代码简洁和可读。 |
205+
| **致命的程序缺陷** | **`panic!`** | 用于指示不可恢复的状态,例如违反了代码不变量。避免在库的公共 API 中使用。 |

0 commit comments

Comments
 (0)