Error Handling in Rust
Use Result<T, E> for recoverable errors and panic! for unrecoverable bugs. The ? operator propagates errors succinctly.
Result and ?
use std::fs::File; use std::io::{self, Read};
fn read_all(path: &str) -> io::Result<String> {
let mut s = String::new();
File::open(path)?.read_to_string(&mut s)?;
Ok(s)
}
? converts the error into the function’s return type via From/Into or From implementations.
Defining error types
Use enums to represent domain errors; derive Debug/thiserror::Error for ergonomics.
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("io: {0}")] Io(#[from] std::io::Error),
#[error("parse: {0}")] ParseInt(#[from] std::num::ParseIntError),
}
Anyhow for apps
anyhow::Result<T> provides easy error aggregation for applications (not libraries).
fn run() -> anyhow::Result<()> {
let n: i32 = std::env::var("N")?.parse()?;
println!("{n}");
Ok(())
}
panics
Use panic! for bugs (violated invariants). Catch panics in tests; avoid panicking in libraries where callers can recover.
Logging and context
Attach context for easier debugging.
use anyhow::Context;
File::open(path).with_context(|| format!("opening {path}"))?;
Summary
- Prefer
Resultwith?for recoverable errors - Define error enums (thiserror) for libraries; use anyhow for apps
- Add context; reserve
panic!for bugs/invariants