Custom Error Types / thiserror
In Rust, you can define custom error types to represent application-specific errors in a type-safe way. The basic pattern is to define error variants using an enum and provide error messages by implementing the Display trait.
Syntax
use std::fmt;
use std::error::Error;
// Define a custom error type.
#[derive(Debug)]
enum AppError {
NotFound(String),
ParseError(String),
IoError(std::io::Error),
}
// Implement the Display trait to define error messages.
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::NotFound(s) => write!(f, "Not found: {}", s),
AppError::ParseError(s) => write!(f, "Parse error: {}", s),
AppError::IoError(e) => write!(f, "I/O error: {}", e),
}
}
}
// Implement the Error trait (an empty implementation is fine).
impl Error for AppError {}
Error Design Patterns
| Pattern | Description |
|---|---|
| enum MyError { ... } | Defines error kinds as enum variants. |
| impl Display for MyError | Defines a user-facing error message (required). |
| impl Error for MyError {} | Marks the type as an error trait implementor (empty body is fine). |
| impl From<io::Error> for MyError | Enables automatic conversion from another error type (allows use of the ? operator). |
| Box<dyn Error> | A trait object used when you need to return multiple different error types. |
| type Result<T> = std::result::Result<T, MyError> | Defines a type alias for Result to omit the error type annotation. |
Sample Code
use std::fmt;
use std::error::Error;
use std::num::ParseIntError;
#[derive(Debug)]
enum CalcError {
DivisionByZero,
Overflow,
ParseError(ParseIntError),
}
impl fmt::Display for CalcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CalcError::DivisionByZero => write!(f, "Division by zero error"),
CalcError::Overflow => write!(f, "Overflow occurred"),
CalcError::ParseError(e) => write!(f, "Number parse error: {}", e),
}
}
}
impl Error for CalcError {}
// Automatically converts ParseIntError into CalcError (enables use of the ? operator).
impl From<ParseIntError> for CalcError {
fn from(e: ParseIntError) -> Self {
CalcError::ParseError(e)
}
}
// Custom Result type alias.
type CalcResult<T> = Result<T, CalcError>;
fn divide(a: i32, b: i32) -> CalcResult<i32> {
if b == 0 {
Err(CalcError::DivisionByZero)
} else {
Ok(a / b)
}
}
// The ? operator automatically converts ParseIntError into CalcError.
fn parse_and_divide(a: &str, b: &str) -> CalcResult<i32> {
let a: i32 = a.parse()?; // ParseIntError is converted to CalcError::ParseError.
let b: i32 = b.parse()?;
divide(a, b)
}
fn main() {
// Normal calculation.
match divide(10, 2) {
Ok(result) => println!("10 / 2 = {}", result),
Err(e) => println!("Error: {}", e),
}
// Division by zero.
match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Calculation involving string parsing.
let cases = vec![
("20", "4"),
("abc", "4"),
("20", "0"),
];
for (a, b) in cases {
match parse_and_divide(a, b) {
Ok(result) => println!("{} / {} = {}", a, b, result),
Err(e) => println!("{} / {} error: {} ({:?})", a, b, e, e),
}
}
// A simpler version using Box<dyn Error> (can return multiple error types).
fn flexible_error(fail: bool) -> Result<i32, Box<dyn Error>> {
if fail {
Err("simple error".into())
} else {
Ok(42)
}
}
println!("{:?}", flexible_error(false));
println!("{:?}", flexible_error(true));
}
Notes
If you implement From<OtherError> on your custom error type, the ? operator will automatically convert errors for you. This lets you write clean, concise code even in functions that can produce multiple kinds of errors.
For production applications, the thiserror crate is very convenient. Its #[derive(Error)] macro automatically generates the Display and Error implementations.
For basic Result operations, see Result::unwrap() / expect() / ? operator.
If you find any errors or copyright issues, please contact us.