Error Patterns
Idiomatic error handling with thiserror and anyhow.
Overview
Rust’s error handling is explicit and type-safe. The ecosystem provides powerful crates that make error handling ergonomic while maintaining these guarantees. Understanding when to use which approach is key to writing maintainable Rust code.
flowchart TB
subgraph "Error Handling Ecosystem"
STD[std::error::Error]
TH[thiserror]
AH[anyhow]
EY[color-eyre]
end
STD --> STD1["Base trait<br/>Manual impl"]
TH --> TH1["Derive macro<br/>Custom errors"]
AH --> AH1["Flexible type<br/>Quick prototyping"]
EY --> EY1["Pretty errors<br/>CLI apps"]
TH -->|"implements"| STD
AH -->|"wraps any"| STD
EY -->|"extends"| AH
style TH fill:#c8e6c9
style AH fill:#e3f2fd
When to Use Each Approach
flowchart TD
A[Error Handling Decision] --> B{Library or<br/>Application?}
B -->|Library| C{Need specific<br/>error types?}
B -->|Application| D{Priority?}
C -->|Yes| E[thiserror]
C -->|No| F["Box<dyn Error>"]
D -->|Quick development| G[anyhow]
D -->|Pretty CLI output| H[color-eyre]
D -->|Specific handling| I[thiserror + anyhow]
style E fill:#c8e6c9
style G fill:#e3f2fd
style H fill:#fff3e0
Error Handling Strategy:
- Libraries: Use
thiserrorfor specific, documented error types- Applications: Use
anyhowfor flexible error propagation- Both: Always add context when propagating errors
- CLI apps: Consider
color-eyrefor user-friendly output
Custom Error Types with thiserror
The thiserror crate derives std::error::Error implementations.
use thiserror::Error;
#[derive(Error, Debug)]
enum DataError {
#[error("file not found: {0}")]
NotFound(String),
#[error("parse error at line {line}: {message}")]
Parse { line: usize, message: String },
#[error("io error")]
Io(#[from] std::io::Error),
#[error("invalid input: {0}")]
Invalid(#[source] Box<dyn std::error::Error + Send + Sync>),
}
fn load_data(path: &str) -> Result<String, DataError> {
if path.is_empty() {
return Err(DataError::NotFound("empty path".into()));
}
let content = std::fs::read_to_string(path)?; // Auto-converts io::Error
Ok(content)
}
Add to Cargo.toml:
[dependencies]
thiserror = "1"
Quick Prototyping with anyhow
The anyhow crate provides a flexible error type for applications.
use anyhow::{Context, Result, bail, ensure};
fn read_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.context("failed to read config file")?;
let config: Config = serde_json::from_str(&content)
.context("failed to parse config")?;
ensure!(config.port > 0, "port must be positive");
if config.name.is_empty() {
bail!("name cannot be empty");
}
Ok(config)
}
fn main() -> Result<()> {
let config = read_config("config.json")?;
println!("Loaded: {:?}", config);
Ok(())
}
Add to Cargo.toml:
[dependencies]
anyhow = "1"
thiserror vs anyhow
| Crate | Use For |
|---|---|
thiserror |
Libraries, specific error types |
anyhow |
Applications, quick prototyping |
graph LR
A[Library Code] --> B[thiserror]
C[Application Code] --> D[anyhow]
B --> E[Specific errors for API]
D --> F[Easy error propagation]
Error Context Chains
use anyhow::{Context, Result};
fn process_file(path: &str) -> Result<()> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path))?;
let data = parse(&content)
.context("failed to parse content")?;
save(&data)
.context("failed to save processed data")?;
Ok(())
}
fn main() {
if let Err(e) = process_file("data.txt") {
// Prints full error chain
eprintln!("Error: {:#}", e);
// Iterate through causes
for cause in e.chain() {
eprintln!(" Caused by: {}", cause);
}
}
}
Downcasting Errors
use anyhow::Result;
use std::io;
fn handle_error(result: Result<()>) {
if let Err(e) = result {
// Try to downcast to specific error type
if let Some(io_err) = e.downcast_ref::<io::Error>() {
match io_err.kind() {
io::ErrorKind::NotFound => println!("File not found"),
io::ErrorKind::PermissionDenied => println!("Access denied"),
_ => println!("IO error: {}", io_err),
}
} else {
println!("Other error: {}", e);
}
}
}
Error Wrapping Pattern
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("configuration error")]
Config(#[source] ConfigError),
#[error("database error")]
Database(#[source] DatabaseError),
#[error("network error")]
Network(#[source] NetworkError),
}
// Specific error types for each subsystem
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("missing field: {0}")]
MissingField(String),
#[error("invalid value: {0}")]
InvalidValue(String),
}
impl From<ConfigError> for AppError {
fn from(e: ConfigError) -> Self {
AppError::Config(e)
}
}
Result Type Aliases
// In your library's error module
pub type Result<T> = std::result::Result<T, Error>;
// Usage in library code
pub fn connect() -> Result<Connection> {
// ...
}
Error Reporting
For CLI applications, use color-eyre for pretty error reports.
use color_eyre::eyre::Result;
fn main() -> Result<()> {
color_eyre::install()?;
// Your application code
process_data()?;
Ok(())
}
Error Design Patterns
flowchart LR
subgraph "Error Architecture"
direction TB
L1[Public API Error]
L2[Module Errors]
L3[External Errors]
end
L3 -->|"#[from]"| L2
L2 -->|"#[source]"| L1
L1 --> U[User sees<br/>high-level error]
L3 --> D[Preserves<br/>original cause]
style L1 fill:#c8e6c9
style U fill:#e3f2fd
Best Practices
Error Handling Guidelines:
- Libraries: Use
thiserrorwith specific error enums- Applications: Use
anyhowfor flexible error handling- Always add context when propagating errors
- Don’t lose information - use
#[from]and#[source]- Use
bail!andensure!for cleaner error creation- Document error conditions in public API functions
Common Mistakes
Avoid these error handling anti-patterns:
- Using
.unwrap()in library code (panic on error)- Losing error context by converting to strings early
- Creating too many error variants (keep it simple)
- Forgetting to implement
Send + Syncfor thread-safe errors
Summary
| Pattern | Code |
|---|---|
| Custom error | #[derive(Error)] |
| Wrap io::Error | #[error("...")] Io(#[from] io::Error) |
| Add context | .context("message")? |
| Early return | bail!("error message") |
| Assert condition | ensure!(cond, "message") |
See Also
- Error Handling - Fundamental error handling with Result
- Error Handling Libraries - Library reference
- Example Code
Next Steps
Learn about State Machine patterns with enums.