Rust Error Handling
Comprehensive guide based on The Rust Programming Language Ch. 9, Effective Rust Item 4, and Rust by Example.
When to Use This Skill
- Choosing between
panic!,Result, andOption - Designing custom error types for a library or application
- Composing multiple error types with
? - Deciding between
thiserror(libraries) andanyhow(applications) - Converting errors with
From/Into - Wrapping context around errors
Decision Tree
Is recovery possible?
├── No → panic! / unwrap / expect (bugs, invariant violations)
└── Yes
├── Value might be absent (not an error) → Option<T>
└── Operation can fail → Result<T, E>
├── Library crate → custom enum + thiserror
└── Application → anyhow::Result or Box<dyn Error>
The ? Operator
Propagates errors by early-returning Err(e.into()):
use std::fs;
use std::io;
fn read_config() -> Result<String, io::Error> {
let path = fs::read_to_string("config.toml")?; // returns Err on failure
Ok(path)
}
? calls From::from(err) on the error, enabling automatic conversion.
Custom Error Types (Library Pattern)
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("invalid header at byte {offset}")]
InvalidHeader { offset: usize },
#[error("unsupported version {0}")]
UnsupportedVersion(u32),
#[error(transparent)]
Io(#[from] std::io::Error),
}
Key rules:
- Implement
std::error::Error(thiserror does this automatically) - Implement
Displayfor user-facing messages - Implement
From<SubError>for each wrapped error type - Keep variants domain-specific and actionable
Application Error Pattern (anyhow)
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = std::fs::read_to_string("app.toml")
.context("failed to read config file")?;
let parsed = parse_config(&config)
.context("config parsing failed")?;
run(parsed)?;
Ok(())
}
anyhow provides:
anyhow::Error— type-erased, carries a chain of context.context("msg")— wraps error with human-readable description.with_context(|| format!(...))— lazy context formatting- Downcasting:
err.downcast_ref::<MyError>()
Option Combinators
let port: Option<u16> = env::var("PORT").ok()
.and_then(|s| s.parse().ok());
let port = port.unwrap_or(8080); // default fallback
| Method | Purpose |
|---|---|
map(f) |
Transform inner value |
and_then(f) |
Chain fallible transforms |
unwrap_or(v) |
Default value |
unwrap_or_else(f) |
Lazy default |
ok_or(e) |
Convert to Result |
transpose() |
Option<Result> ↔ Result<Option> |
Result Combinators
| Method | Purpose |
|---|---|
map(f) |
Transform Ok value |
map_err(f) |
Transform Err value |
and_then(f) |
Chain fallible operations |
or_else(f) |
Try alternative on error |
unwrap_or_default() |
Use Default on error |
context("msg") |
(anyhow) Add context |
When to panic!
- Invariant violations that indicate bugs (not user input errors)
- Setup/initialization that can't recover (missing required env vars at startup)
- Tests and examples
unreachable!()for logically impossible branches- Prototype code (prefer
todo!()overunwrap())
Error Composition Pattern
#[derive(Debug, Error)]
pub enum AppError {
#[error("database error")]
Db(#[from] diesel::result::Error),
#[error("network error")]
Network(#[from] reqwest::Error),
#[error("validation: {0}")]
Validation(String),
}
Reference Map
references/result-option.md— Result/Option APIs, combinators, conversionreferences/custom-errors.md— designing error enums, thiserror patternsreferences/anyhow-context.md— application-level error handling, context chains
Key References
Source: botirk38/botir-skills — distributed by TomeVault.