Skip to content

Custom Error Types

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.

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),
}

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),
}
}
}

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,
}
}
}

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),
}
}
}
Which trait provides the human-readable error message shown with {}?
What is the purpose of implementing From<OtherError> for MyError?
What does source() return in std::error::Error?
Why is a concrete error enum better than Box<dyn Error> for a library API?