---
description: Rust Error Handling
alwaysApply: false
---

# Rust Error Handling

`Result<T, E>` and `Option<T>` are the primitives. No exceptions — every error path is explicit.

## thiserror (Libraries)

```rust
#[derive(Debug, Error)]
pub enum AppError {
    #[error("database query failed")]
    Database(#[from] sqlx::Error),
    #[error("record not found: {entity} with id {id}")]
    NotFound { entity: &'static str, id: String },
    #[error("validation failed: {0}")]
    Validation(String),
}
```

## anyhow (Applications)

```rust
fn load_config(path: &Path) -> Result<Config> {
    let contents = fs::read_to_string(path)
        .with_context(|| format!("reading config from {}", path.display()))?;
    ensure!(!config.database_url.is_empty(), "database_url must not be empty");
    Ok(toml::from_str(&contents).context("parsing config")?)
}
```

## When to Use Which

- **thiserror** — libraries, when callers need to match on error variants
- **anyhow** — applications, when you just need to propagate context
- **Manual impl** — when you need full control or minimal dependencies

## The ? Operator

```rust
fn process(path: &Path) -> Result<Data, AppError> {
    let raw = fs::read(path)?;                    // io::Error → AppError via From
    let parsed = serde_json::from_slice(&raw)?;    // auto-conversion
    Ok(parsed)
}
```

## Option Patterns

```rust
let name = user.name.as_deref().unwrap_or("anonymous");
let parsed = input.strip_prefix("v").and_then(|s| s.parse::<u32>().ok());
if let Some(token) = headers.get("Authorization") {
    authenticate(token)?;
}
```

## Panic Policy

- Panics are for bugs, not expected errors
- `debug_assert!` for debug-only invariants
- `unwrap()` OK in tests; never in library code without justification
- `todo!()` never ships

## Anti-Patterns

```rust
// Never: unwrap() with no context — use .expect("reason")
// Never: String as error type — use thiserror or anyhow
fn process() -> Result<(), String> { Err("bad".into()) }
// Never: Catching panics as error handling
let _ = std::panic::catch_unwind(|| dangerous());
```
