Error Handling Patterns in Rust

Error Handling Patterns in Rust

Rust's error handling is one of its greatest strengths, but it can feel overwhelming when you're starting out. Let's break down the patterns.

THE_BASICS

Every Rust developer knows Result<T, E> and the ? operator. But choosing the right error type is where things get interesting.

fn parse_config(path: &str) -> Result<Config, ConfigError> {
    let content = std::fs::read_to_string(path)?;
    let config: Config = toml::from_str(&content)?;
    Ok(config)
}

CUSTOM_ERROR_TYPES

For libraries, define explicit error enums:

#[derive(Debug, thiserror::Error)]
enum AppError {
    #[error("config error: {0}")]
    Config(#[from] ConfigError),
    #[error("network error: {0}")]
    Network(#[from] reqwest::Error),
    #[error("not found: {0}")]
    NotFound(String),
}

ANYHOW_VS_THISERROR

  • Use thiserror in libraries — callers need typed errors
  • Use anyhow in applications — you just need context and backtraces

ERROR_CONTEXT

Always add context to your errors. Future you will thank present you.

let config = parse_config("app.toml")
    .context("failed to load application config")?;

BEST_PRACTICES

  1. Never use unwrap() in production code
  2. Map errors at module boundaries
  3. Include enough context to debug without a stack trace
  4. Use #[from] for automatic conversion