Custom Error Types
Why Custom Errors?
Section titled “Why Custom Errors?”Using String or Box<dyn std::error::Error> as your error type is fine for quick scripts, but real libraries and applications benefit from a concrete error enum:
- Callers can pattern-match on specific variants and handle each case differently.
- The error type documents every failure mode of your API.
- You can attach structured data (e.g., the invalid value) to each variant.
Defining an Error Enum
Section titled “Defining an Error Enum”Declare an enum with one variant per failure mode. Derive Debug so the error can be printed with {:?}:
#[derive(Debug)]enum AppError { ParseError(std::num::ParseIntError), NegativeNumber(i32), TooBig(i32),}Implementing Display
Section titled “Implementing Display”The std::fmt::Display trait controls the human-readable error message — what you see when you print with {}:
use std::fmt;
impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AppError::ParseError(e) => write!(f, "parse error: {}", e), AppError::NegativeNumber(n) => write!(f, "number is negative: {}", n), AppError::TooBig(n) => write!(f, "number is too big: {} (max 100)", n), } }}Implementing std::error::Error
Section titled “Implementing std::error::Error”To be usable wherever a dyn Error is expected (e.g., in Box<dyn Error>), your type must implement std::error::Error. The trait has no required methods — the default implementations are sufficient for most cases. Optionally implement source() to expose the underlying cause:
impl std::error::Error for AppError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { AppError::ParseError(e) => Some(e), _ => None, } }}Automatic Conversion with From
Section titled “Automatic Conversion with From”Implementing From<SomeOtherError> for AppError lets the ? operator convert automatically when calling functions that return SomeOtherError:
impl From<std::num::ParseIntError> for AppError { fn from(e: std::num::ParseIntError) -> Self { AppError::ParseError(e) }}Now s.parse::<i32>()? in a function returning Result<_, AppError> will automatically wrap the ParseIntError into AppError::ParseError.
use std::fmt;use std::num::ParseIntError;
#[derive(Debug)]enum AppError { ParseError(ParseIntError), NegativeNumber(i32), TooBig(i32),}
impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AppError::ParseError(e) => write!(f, "parse error: {}", e), AppError::NegativeNumber(n) => write!(f, "number is negative: {}", n), AppError::TooBig(n) => write!(f, "number is too big: {} (max 100)", n), } }}
impl std::error::Error for AppError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { AppError::ParseError(e) => Some(e), _ => None, } }}
impl From<ParseIntError> for AppError { fn from(e: ParseIntError) -> Self { AppError::ParseError(e) }}
fn parse_bounded(s: &str) -> Result<i32, AppError> { let n: i32 = s.parse()?; // ParseIntError -> AppError via From if n < 0 { return Err(AppError::NegativeNumber(n)); } if n > 100 { return Err(AppError::TooBig(n)); } Ok(n)}
fn main() { let cases = ["42", "-5", "200", "abc"]; for s in &cases { match parse_bounded(s) { Ok(n) => println!("{} -> Ok({})", s, n), Err(e) => println!("{} -> Err: {}", s, e), } }}Compiling…