Rust Option and Result key takeaways / Rust Option 与 Result 核心要点
What you’ll learn / 你将学到: Idiomatic error handling patterns — safe alternatives to
unwrap(), the?operator for propagation, custom error types, and when to useanyhowvsthiserrorin production code.地道的错误处理模式 ——
unwrap()的安全替代方案、用于错误传播的?运算符、自定义错误类型,以及在生产代码中何时使用anyhow与thiserror。
OptionandResultare an integral part of idiomatic Rust /Option和Result是地道 Rust 代码中不可或缺的一部分-
- Safe alternatives to
unwrap():
- Safe alternatives to
-
- Safe alternatives to
unwrap()/unwrap()的安全替代方案:
- Safe alternatives to
#![allow(unused)]
fn main() {
-// Option<T> safe alternatives
+// Option<T> safe alternatives / Option<T> 的安全替代方案
- let value = opt.unwrap_or(default); // Provide fallback value
+ let value = opt.unwrap_or(default); // Provide fallback value / 提供备选值
- let value = opt.unwrap_or_else(|| compute()); // Lazy computation for fallback
+ let value = opt.unwrap_or_else(|| compute()); // Lazy computation / 惰性计算备选值
- let value = opt.unwrap_or_default(); // Use Default trait implementation
+ let value = opt.unwrap_or_default(); // Use Default trait / 使用 Default trait 的实现
- let value = opt.expect("descriptive message"); // Only when panic is acceptable
+ let value = opt.expect("descriptive message"); // Only when panic is acceptable / 仅当允许 panic 时使用,带描述信息
-// Result<T, E> safe alternatives
+// Result<T, E> safe alternatives / Result<T, E> 的安全替代方案
- let value = result.unwrap_or(fallback); // Ignore error, use fallback
+ let value = result.unwrap_or(fallback); // Ignore error / 忽略错误,使用备选值
- let value = result.unwrap_or_else(|e| handle(e)); // Handle error, return fallback
+ let value = result.unwrap_or_else(|e| handle(e)); // Handle error / 处理错误并返回备选值
- let value = result.unwrap_or_default(); // Use Default trait
+ let value = result.unwrap_or_default(); // Use Default trait / 使用 Default trait
}
-
- Pattern matching for explicit control:
-
- Pattern matching for explicit control / 用于显式控制的模式匹配:
#![allow(unused)]
fn main() {
match some_option {
- Some(value) => println!("Got: {}", value),
+ Some(value) => println!("Got: {}", value), // 匹配到值
- None => println!("No value found"),
+ None => println!("No value found"), // 无值
}
match some_result {
- Ok(value) => process(value),
+ Ok(value) => process(value), // 成功
- Err(error) => log_error(error),
+ Err(error) => log_error(error), // 错误
}
}
-
- Use
?operator for error propagation: Short-circuit and bubble up errors
- Use
-
- Use
?operator for error propagation / 使用?运算符进行错误传播:短路并向上抛出错误
- Use
#![allow(unused)]
fn main() {
fn process_file(path: &str) -> Result<String, std::io::Error> {
- let content = std::fs::read_to_string(path)?; // Automatically returns error
+ let content = std::fs::read_to_string(path)?; // Automatically returns error / 自动返回错误
Ok(content.to_uppercase())
}
}
-
- Transformation methods:
-
- Transformation methods / 转换方法:
-
- `map()`: Transform the success value `Ok(T)` -> `Ok(U)` or `Some(T)` -> `Some(U)`
-
- `map()`:转换成功值 `Ok(T)` -> `Ok(U)` 或 `Some(T)` -> `Some(U)`
-
- `map_err()`: Transform the error type `Err(E)` -> `Err(F)`
-
- `map_err()`:转换错误类型 `Err(E)` -> `Err(F)`
-
- `and_then()`: Chain operations that can fail
-
- `and_then()`:链接可能失败的操作
-
- Use in your own APIs: Prefer
Result<T, E>over exceptions or error codes
- Use in your own APIs: Prefer
-
- Use in your own APIs / 在你自己的 API 中使用:优先使用
Result<T, E>而非异常或错误码
- Use in your own APIs / 在你自己的 API 中使用:优先使用
-
- References: Option docs | Result docs
-
- Borrowing issues: Most common beginner mistake
-
- Borrowing issues / 借用问题:最常见的初学者错误
-
- "cannot borrow as mutable" -> Only one mutable reference allowed at a time
-
- "cannot borrow as mutable" -> 同一时间只允许一个可变引用
-
- "borrowed value does not live long enough" -> Reference outlives the data it points to
-
- "borrowed value does not live long enough" -> 引用的存活时间超过了它指向的数据
-
- **Fix**: Use scopes `{}` to limit reference lifetimes, or clone data when needed
-
- **Fix / 修复**:使用作用域 `{}` 限制引用生命周期,或在需要时克隆数据
-
- Missing trait implementations: “method not found” errors
-
- Missing trait implementations / 缺失 Trait 实现:“method not found” 错误
-
- **Fix**: Add `#[derive(Debug, Clone, PartialEq)]` for common traits
-
- **Fix / 修复**:为常用 trait 添加 `#[derive(Debug, Clone, PartialEq)]`
-
- Use `cargo check` to get better error messages than `cargo run`
-
- 使用 `cargo check` 获得比 `cargo run` 更好的错误消息
-
- Integer overflow in debug mode: Rust panics on overflow
-
- Integer overflow in debug mode / 调试模式下的整数溢出:Rust 在溢出时会发生 panic
-
- **Fix**: Use `wrapping_add()`, `saturating_add()`, or `checked_add()` for explicit behavior
-
- **Fix / 修复**:使用 `wrapping_add()`、`saturating_add()` 或 `checked_add()` 来指定明确的行为
-
- String vs &str confusion: Different types for different use cases
-
- String vs &str confusion / String 与 &str 的混淆:不同用例使用不同类型
-
- Use `&str` for string slices (borrowed), `String` for owned strings
-
- `&str` 用于字符串切片(借用),`String` 用于拥有所有权的字符串
-
- **Fix**: Use `.to_string()` or `String::from()` to convert `&str` to `String`
-
- **Fix / 修复**:使用 `.to_string()` 或 `String::from()` 将 `&str` 转换为 `String`
-
- Fighting the borrow checker: Don’t try to outsmart it
-
- Fighting the borrow checker / 与借用检查器对抗:不要试图自作聪明
-
- **Fix**: Restructure code to work with ownership rules rather than against them
-
- **Fix / 修复**:重构代码以适应所有权规则,而不是违背它们
-
- Consider using `Rc<RefCell<T>>` for complex sharing scenarios (sparingly)
-
- 对于复杂的共享场景,考虑(谨慎地)使用 `Rc<RefCell<T>>`
#![allow(unused)]
fn main() {
- // [ERROR] BAD: Can panic unexpectedly
+ // [ERROR] BAD: Can panic unexpectedly / [劣] 可能意外触发 Panic
fn bad_config_reader() -> String {
- let config = std::env::var("CONFIG_FILE").unwrap(); // Panic if not set!
+ let config = std::env::var("CONFIG_FILE").unwrap(); // Panic if not set! / 未设置则 Panic!
- std::fs::read_to_string(config).unwrap() // Panic if file missing!
+ std::fs::read_to_string(config).unwrap() // Panic if file missing! / 文件缺失则 Panic!
}
- // [OK] GOOD: Handles errors gracefully
+ // [OK] GOOD: Handles errors gracefully / [优] 优雅处理错误
fn good_config_reader() -> Result<String, ConfigError> {
let config_path = std::env::var("CONFIG_FILE")
- .unwrap_or_else(|_| "default.conf".to_string()); // Fallback to default
+ .unwrap_or_else(|_| "default.conf".to_string()); // Fallback / 备选默认值
let content = std::fs::read_to_string(config_path)
- .map_err(ConfigError::FileRead)?; // Convert and propagate error
+ .map_err(ConfigError::FileRead)?; // Convert and propagate / 转换并传播错误
Ok(content)
}
- // [OK] EVEN BETTER: With proper error types
+ // [OK] EVEN BETTER: With proper error types / [更佳] 使用适当的错误类型
use thiserror::Error;
#[derive(Error, Debug)]
enum ConfigError {
#[error("Failed to read config file: {0}")]
FileRead(#[from] std::io::Error),
#[error("Invalid configuration: {message}")]
Invalid { message: String },
}
}
- Let’s break down what’s happening here.
ConfigErrorhas just two variants — one for I/O errors and one for validation errors. This is the right starting point for most modules:
- 让我们分析一下这里发生了什么。
ConfigError只有两个变体 —— 一个用于 I/O 错误,另一个用于验证错误。这是大多数模块的正确起点:
-| ConfigError variant | Holds | Created by |
+| ConfigError variant / 变体 | Holds / 持有内容 | Created by / 创建者 |
|–––––––––––|—––|———–|
-| FileRead(io::Error) | The original I/O error | #[from] auto-converts via ? |
+| FileRead(io::Error) | The original I/O error / 原始 I/O 错误 | #[from] auto-converts via ? / 通过 ? 自动转换 |
-| Invalid { message } | A human-readable explanation | Your validation code |
+| Invalid { message } | A human-readable explanation / 人类可读的解释 | Your validation code / 你的验证代码 |
- Now you can Write functions that return
Result<T, ConfigError>:
- 现在你可以编写返回
Result<T, ConfigError>的函数了:
#![allow(unused)]
fn main() {
fn read_config(path: &str) -> Result<String, ConfigError> {
- let content = std::fs::read_to_string(path)?; // io::Error → ConfigError::FileRead
+ let content = std::fs::read_to_string(path)?; // io::Error → ConfigError::FileRead / 自动转换
if content.is_empty() {
return Err(ConfigError::Invalid {
message: "config file is empty".to_string(),
});
}
Ok(content)
}
}
-
🟢 Self-study checkpoint: Before continuing, make sure you can answer:
-
🟢 Self-study checkpoint / 自学检查点:在继续之前,请确保你能回答:
-
- Why does
?on theread_to_stringcall work? (Because#[from]generatesimpl From<io::Error> for ConfigError)
- Why does
-
- 为什么
read_to_string调用上的?能奏效?(因为#[from]生成了impl From<io::Error> for ConfigError)
- 为什么
-
- What happens if you add a third variant
MissingKey(String)— what code changes? (Just add the variant; existing code still compiles)
- What happens if you add a third variant
-
- 如果你添加了第三个变体
MissingKey(String),会发生什么 —— 哪些代码需要更改?(只需加上变体;现有代码依然可以编译)
- 如果你添加了第三个变体
- As your project grows beyond a single file, you’ll combine multiple module-level errors into a crate-level error type. This is the standard pattern in production Rust. Let’s build up from the
ConfigErrorabove.
- 随着项目规模超过单个文件,你将把多个模块级错误组合成一个 crate 级错误类型。这是生产级 Rust 中的标准模式。让我们在上面的
ConfigError基础上继续构建。
- In real-world Rust projects, every crate (or significant module) defines its own
Error
- 在现实世界的 Rust 项目中,每个 crate(或重要模块)都会定义自己的
Error
- enum and a
Resulttype alias. This is the idiomatic pattern — analogous to how in C++
- 枚举和一个
Result类型别名。这是地道的模式 —— 类似于在 C++ 中
- you’d define a per-library exception hierarchy and
using Result = std::expected<T, Error>.
- 你会为每个库定义异常层次结构以及
using Result = std::expected<T, Error>。
#![allow(unused)]
fn main() {
- // src/error.rs (or at the top of lib.rs)
+ // src/error.rs (或者在 lib.rs 的顶部)
use thiserror::Error;
- /// Every error this crate can produce.
+ /// Every error this crate can produce / 本 crate 可能产生的所有错误
#[derive(Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
- Io(#[from] std::io::Error), // auto-converts via From
+ Io(#[from] std::io::Error), // auto-converts / 通过 From 自动转换
#[error("JSON parse error: {0}")]
- Json(#[from] serde_json::Error), // auto-converts via From
+ Json(#[from] serde_json::Error), // auto-converts / 通过 From 自动转换
#[error("Invalid sensor id: {0}")]
- InvalidSensor(u32), // domain-specific variant
+ InvalidSensor(u32), // domain-specific / 领域特定变体
#[error("Timeout after {ms} ms")]
Timeout { ms: u64 },
}
- /// Crate-wide Result alias — saves typing throughout the crate.
+ /// Crate-wide Result alias / Crate 范围的 Result 别名 —— 减少整个 crate 中的输入
pub type Result<T> = core::result::Result<T, Error>;
}
- Without the alias you’d write:
- 如果没有别名,你会写:
#![allow(unused)]
fn main() {
- // Verbose — error type repeated everywhere
+ // Verbose / 冗长 —— 错误类型到处重复
fn read_sensor(id: u32) -> Result<f64, crate::Error> { ... }
fn parse_config(path: &str) -> Result<Config, crate::Error> { ... }
}
- With the alias:
- 有了别名:
#![allow(unused)]
fn main() {
- // Clean — just `Result<T>`
+ // Clean / 简洁 —— 只需 `Result<T>`
use crate::{Error, Result};
fn read_sensor(id: u32) -> Result<f64> {
if id > 128 {
return Err(Error::InvalidSensor(id));
}
- let raw = std::fs::read_to_string(format!("/dev/sensor/{id}"))?; // io::Error → Error::Io
+ let raw = std::fs::read_to_string(format!("/dev/sensor/{id}"))?; // 自动转换:io::Error → Error::Io
let value: f64 = raw.trim().parse()
- .map_err(|_| Error::InvalidSensor(id))?;
+ .map_err(|_| Error::InvalidSensor(id))?; // 手动映射错误
Ok(value)
}
}
- The
#[from]attribute onIogenerates thisimplfor free:
Io上的#[from]属性免费生成了以下impl:
#![allow(unused)]
fn main() {
- // Auto-generated by thiserror's #[from]
+ // 由 thiserror 的 #[from] 自动生成
impl From<std::io::Error> for Error {
fn from(source: std::io::Error) -> Self {
Error::Io(source)
}
}
}
- That’s what makes
?work: when a function returnsstd::io::Errorand your function
- 这正是
?奏效的原因:当一个函数返回std::io::Error而你的函数
- returns
Result<T>(your alias), the compiler callsFrom::from()to convert it
- 返回
Result<T>(你的别名)时,编译器会自动调用From::from()进行转换。
-
automatically.
-
Composing module-level errors
- Larger crates split errors by module, then compose them at the crate root:
- 较大的 crate 会按模块拆分错误,然后在 crate 根部进行组合:
#![allow(unused)]
fn main() {
// src/config/error.rs
#[derive(thiserror::Error, Debug)]
pub enum ConfigError {
#[error("Missing key: {0}")]
MissingKey(String),
#[error("Invalid value for '{key}': {reason}")]
InvalidValue { key: String, reason: String },
}
- // src/error.rs (crate-level)
+ // src/error.rs (crate 级)
#[derive(thiserror::Error, Debug)]
pub enum Error {
- #[error(transparent)] // delegates Display to inner error
+ #[error(transparent)] // delegates Display / 将 Display 委托给内部错误
Config(#[from] crate::config::ConfigError),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
pub type Result<T> = core::result::Result<T, Error>;
}
- Callers can still match on specific config errors:
- 调用者仍然可以对特定的配置错误进行匹配:
#![allow(unused)]
fn main() {
match result {
- Err(Error::Config(ConfigError::MissingKey(k))) => eprintln!("Add '{k}' to config"),
+ Err(Error::Config(ConfigError::MissingKey(k))) => eprintln!("Add '{k}' to config / 请在配置中添加 '{k}'"),
- Err(e) => eprintln!("Other error: {e}"),
+ Err(e) => eprintln!("Other error: {e} / 其他错误:{e}"),
Ok(v) => use_value(v),
}
}
-| Concept | C++ | Rust |
+| Concept / 概念 | C++ | Rust |
|———|—–|——|
-| Error hierarchy | class AppError : public std::runtime_error | #[derive(thiserror::Error)] enum Error { ... } |
+| Error hierarchy / 错误层次 | class AppError : public std::runtime_error | #[derive(thiserror::Error)] enum Error { ... } |
-| Return error | std::expected<T, Error> or throw | fn foo() -> Result<T> |
+| Return error / 返回错误 | std::expected<T, Error> or throw | fn foo() -> Result<T> |
-| Convert error | Manual try/catch + rethrow | #[from] + ? — zero boilerplate / 零样板代码 |
+| Convert error / 转换错误 | Manual try/catch + rethrow | #[from] + ? |
-| Result alias | template<class T> using Result = std::expected<T, Error>; | pub type Result<T> = core::result::Result<T, Error>; |
+| Result alias / 结果别名 | template<class T> ... | pub type Result<T> = ... |
-| Error message | Override what() | #[error("...")] — compiled into Display impl |
+| Error message / 错误消息 | Override what() | #[error("...")] — 编译为 Display 实现 |