Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 use anyhow vs thiserror in production code.

地道的错误处理模式 —— unwrap() 的安全替代方案、用于错误传播的 ? 运算符、自定义错误类型,以及在生产代码中何时使用 anyhowthiserror

  • Option and Result are an integral part of idiomatic Rust / OptionResult 是地道 Rust 代码中不可或缺的一部分
    • Safe alternatives to unwrap():
    • Safe alternatives to unwrap() / unwrap() 的安全替代方案
#![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 ? operator for error propagation / 使用 ? 运算符进行错误传播:短路并向上抛出错误
#![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 / 在你自己的 API 中使用:优先使用 Result<T, E> 而非异常或错误码
    • 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. ConfigError has 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 / 自学检查点:在继续之前,请确保你能回答:

    1. Why does ? on the read_to_string call work? (Because #[from] generates impl From<io::Error> for ConfigError)
    1. 为什么 read_to_string 调用上的 ? 能奏效?(因为 #[from] 生成了 impl From<io::Error> for ConfigError
    1. What happens if you add a third variant MissingKey(String) — what code changes? (Just add the variant; existing code still compiles)
    1. 如果你添加了第三个变体 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 ConfigError above.
  • 随着项目规模超过单个文件,你将把多个模块级错误组合成一个 crate 级错误类型。这是生产级 Rust 中的标准模式。让我们在上面的 ConfigError 基础上继续构建。
  • In real-world Rust projects, every crate (or significant module) defines its own Error
  • 在现实世界的 Rust 项目中,每个 crate(或重要模块)都会定义自己的 Error
  • enum and a Result type 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 on Io generates this impl for 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 returns std::io::Error and your function
  • 这正是 ? 奏效的原因:当一个函数返回 std::io::Error 而你的函数
  • returns Result<T> (your alias), the compiler calls From::from() to convert it
  • 返回 Result<T>(你的别名)时,编译器会自动调用 From::from() 进行转换。
  • 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 实现 |