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
- Never use
unwrap()in production code - Map errors at module boundaries
- Include enough context to debug without a stack trace
- Use
#[from]for automatic conversion