# Rust Error Handling

> When to activate: Rust error handling, thiserror, anyhow, custom errors, Result, question mark operator, error propagation

- Skill: `mattakushi432/rust-error-handling` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/rust-error-handling`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/rust-error-handling/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/rust-error-handling

---


# Rust Error Handling

## Custom Errors with thiserror

`thiserror` generates `Display` and `Error` impls from derive macros.

```toml
[dependencies]
thiserror = "2"
```

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("user {id} not found")]
    UserNotFound { id: u64 },

    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("validation failed on field '{field}': {message}")]
    Validation { field: String, message: String },

    #[error("external service '{service}' returned {status}")]
    ExternalService { service: String, status: u16 },

    #[error(transparent)]
    Unexpected(#[from] anyhow::Error),
}
```

## Application Errors with anyhow

`anyhow` is ideal for application code where you need rich context but don't care about the exact type.

```toml
[dependencies]
anyhow = "1"
```

```rust
use anyhow::{Context, Result, bail, ensure};

fn load_config(path: &str) -> Result<Config> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read config from {path}"))?;

    let config: Config = toml::from_str(&content)
        .context("failed to parse config as TOML")?;

    ensure!(config.port > 1024, "port must be > 1024, got {}", config.port);

    if config.workers == 0 {
        bail!("workers must be at least 1");
    }

    Ok(config)
}
```

## The ? Operator

`?` returns early on error, applying `From` conversion if needed.

```rust
fn process() -> Result<Output, AppError> {
    let data = read_file("input.txt")?;
    let parsed = parse_json(&data)?;
    let result = transform(parsed)?;
    Ok(result)
}

// ? works with Option via .ok_or / .ok_or_else
fn find_setting(name: &str) -> Result<String, AppError> {
    settings()
        .get(name)
        .cloned()
        .ok_or_else(|| AppError::Validation {
            field: name.to_string(),
            message: "setting not found".into(),
        })
}
```

## Error Hierarchy (Library vs. Application)

```rust
// Library crates: use thiserror for typed, structured errors
#[derive(Debug, thiserror::Error)]
pub enum MyLibError {
    #[error("invalid input: {0}")]
    InvalidInput(String),
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

// Application code: use anyhow for ergonomic context chains
fn run() -> anyhow::Result<()> {
    my_lib::process("data.txt")
        .context("processing step failed")?;
    Ok(())
}
```

## Converting Between Error Types

```rust
// map_err for targeted conversion
fn parse_id(s: &str) -> Result<u64, AppError> {
    s.parse::<u64>().map_err(|e| AppError::Validation {
        field: "id".into(),
        message: e.to_string(),
    })
}
```

## Error Handling in Main

```rust
fn main() -> anyhow::Result<()> {
    let config = load_config("config.toml")?;
    run(config)?;
    Ok(())
}

// With custom exit codes
fn main() {
    if let Err(e) = run() {
        eprintln!("Error: {e:#}"); // {:#} prints the full error chain
        std::process::exit(1);
    }
}
```

## Common Anti-Patterns

- **Using `Box<dyn Error>` in library APIs** — prevents callers from matching on specific errors
- **`unwrap()` / `expect()` in production code** — always propagate or handle errors explicitly
- **Losing error context with bare `?`** — use `.context()` from `anyhow` to add what operation failed
- **Returning `String` as error type** — hard for callers to programmatically handle
- **Swallowing errors in closures** — log or propagate; never silently discard

