| name | handle-errors-in-code |
| description | Guide for error handling in this Rust project. Covers the four principles (clarity, context, actionability, explicit enums over anyhow), the thiserror pattern for structured errors, including what/where/when/why context, writing actionable help text, and avoiding vague errors. Also covers the located-error package for errors with source location. Use when writing error types, handling Results, adding error variants, or reviewing error messages. Triggers on "error handling", "error type", "Result", "thiserror", "anyhow", "error enum", "error message", "handle error", "add error variant", or "located-error". |
| metadata | {"author":"torrust","version":"1.0"} |
Handling Errors in Code
Core Principles
- Clarity — Users immediately understand what went wrong
- Context — Include what/where/when/why
- Actionability — Tell users how to fix it
- Explicit enums over
anyhow — Prefer structured errors for pattern matching
Prefer Explicit Enum Errors
#[derive(Debug, thiserror::Error)]
pub enum TrackerError {
#[error("Torrent '{info_hash}' not found in whitelist")]
TorrentNotWhitelisted { info_hash: InfoHash },
#[error("Peer limit exceeded for torrent '{info_hash}': max {limit}")]
PeerLimitExceeded { info_hash: InfoHash, limit: usize },
}
return Err(anyhow::anyhow!("Something went wrong"));
return Err("Invalid input".into());
Include Actionable Fix Instructions in Display
When the error is user-facing, add instructions:
#[error(
"Configuration file not found at '{path}'.\n\
Copy the default: cp share/default/config/tracker.toml {path}"
)]
ConfigNotFound { path: PathBuf },
Context Requirements
Each error should answer:
- What: What operation was being performed?
- Where: Which component, file, or resource?
- When: Under what conditions?
- Why: What caused the failure?
#[error("UDP socket bind failed for '{addr}': {source}. Is port {port} already in use?")]
SocketBindFailed { addr: SocketAddr, port: u16, source: std::io::Error },
return Err("bind failed".into());
The located-error Package
For errors that benefit from source location tracking, use the located-error package:
[dependencies]
torrust-located-error = { version = "3.0.0-develop", path = "../located-error" }
use torrust_located_error::Located;
let err = Located(my_error).into();
Unwrap and Expect Policy
| Context | .unwrap() | .expect("msg") | ? / Result |
|---|
| Production code | Never | Only when failure is logically impossible | Default |
| Tests and doc examples | Acceptable | Preferred when message adds clarity | — |
fn load_config(path: &Path) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)
.map_err(|e| ConfigError::FileAccess { path: path.to_path_buf(), source: e })?;
toml::from_str(&content)
.map_err(|e| ConfigError::InvalidToml { path: path.to_path_buf(), source: e })
}
#[test]
fn it_should_parse_valid_config() {
let config = Config::parse(VALID_TOML).unwrap();
assert_eq!(config.http_api.bind_address, "127.0.0.1:1212");
}
Quick Checklist