SuperAPI AI Directory

rust-error-advanced

Coding & development

View repository

Updated 10-16 09:52

About this skill

Result vs Option vs panic

Type When to use Example
Result<T, E> Operations expected to fail File reading, network requests
Option<T> absence is normal Lookups, values that may be empty
panic! bug or invariant violation Program logic errors, unrecoverable errors
unreachable!() Code that theoretically will never be reached Match exhaustiveness

Error handling decision

失败是预期的吗?
    │
    ├─ 是 → 这是库代码?
    │   ├─ 是 → thiserror(类型化错误)
    │   └─ 否 → anyhow(易用性)
    │
    ├─ 否,absence 正常?
    │   └─ Option<T>
    │
    └─ 否,bug 或不变式违规
        └─ panic!, assert!

thiserror (library code)

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("validation failed: {0}")]
    Validation(String),
    
    #[error("IO error: {source}")]
    Io {
        #[from]
        source: std::io::Error,
    },
    
    #[error("not found: {entity}:{id}")]
    NotFound {
        entity: String,
        id: u64,
    },
}

// 使用 ? 传播
fn read_config() -> Result<Config, MyError> {
    let content = std::fs::read_to_string("config.toml")?;
    Ok(toml::from_str(&content)?)
}

anyhow (application code)

use anyhow::{Context, Result, bail};

fn process_user(id: u64) -> Result<User> {
    let user = db.find_user(id)
        .with_context(|| format!("failed to find user {}", id))?;
    
    if !user.is_active {
        bail!("user {} is not active", id);
    }
    
    Ok(user)
}

// 组合多个错误源
fn complex_operation() -> Result<()> {
    let a = operation_a().context("operation A failed")?;
    let b = operation_b().context("operation B failed")?;
    Ok(())
}

Error design principles

Scenario Recommendation
Library code thiserror, precise error types
Application code anyhow, easy to propagate and add context
Library depending on library Propagate third-party errors (#[from])
Need error codes Enum variants
Need error chains context() + with_context()

Common anti-patterns

Anti-pattern Problem Solution
Everywhere unwrap() panic in libraries Use ?
Box<dyn Error> Loss of type information thiserror variants
Loss of context Difficult to debug .context()
Too many error variants Over-engineering Simplify or merge

panic use cases

// 1. 不变量验证(公开 API)
pub fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        panic!("division by zero");  // 公开 API,确保调用者不传入 0
    }
    a / b
}

// 2. 不可恢复错误
fn start_engine() {
    let config = load_critical_config();
    if config.is_corrupted() {
        panic!("cannot start without valid config");
    }
}

// 3. 匹配穷举(理论上的永远执行不到)
fn process_status(status: Status) {
    match status {
        Status::Running => { /* ... */ }
        Status::Stopped => { /* ... */ }
        // 未来可能添加新状态
        // _ => unreachable!("unknown status: {:?}", status),
    }
}

// 4. 内部不变量
assert!(!queue.is_empty(), "queue should never be empty here");

Error chains

// 使用 map_err 转换错误
fn high_level() -> Result<()> {
    low_level()
        .map_err(|e| MyError::from_low_level(e, "high level operation failed"))
}

// 使用 with_context 添加调用链信息
fn middle_layer() -> Result<()> {
    low_level()
        .with_context(|| format!("while processing request {}", request_id))?;
    Ok(())
}

Best practices

  1. Library code: precise error types (thiserror)
  2. Application code: prioritize ease of use (anyhow)
  3. Propagate errors: use ? instead of unwrap()
  4. Add context: use .context() or with_context()
  5. Preserve error source: use #[from] to preserve the underlying error
  6. Distinguish panic scenarios: use panic for bugs, use Result for expected failures