← Back to Rust Course | Chapter 10: Error Handling | Lesson 4 of 6

Custom Error Types

Sometimes you design your own kind of error message, so your program can explain exactly what went wrong in its own words.

Defining a Custom Error Enum

A custom error type is typically an enum listing every distinct way an operation can fail, giving callers structured information instead of just a generic string.

Example: Defining a Custom Error Enum

markup
#[derive(Debug)]
enum MathError {
    DivideByZero,
    NegativeSquareRoot,
}

fn main() {
    let err = MathError::DivideByZero;
    println!("{:?}", err);
}

Returning a Custom Error from a Function

Functions can return Result<T, MyError> using the custom error type, letting the compiler enforce that every failure case is represented in the enum.

Example: Returning a Custom Error from a Function

markup
#[derive(Debug)]
enum MathError {
    DivideByZero,
}

fn divide(a: f64, b: f64) -> Result<f64, MathError> {
    if b == 0.0 {
        Err(MathError::DivideByZero)
    } else {
        Ok(a / b)
    }
}

fn main() {
    println!("{:?}", divide(10.0, 0.0));
}

Implementing Display for Readable Messages

Implementing std::fmt::Display for a custom error type gives it a clean, human-readable message when printed with {} instead of just the raw Debug output.

Example: Implementing Display for Readable Messages

markup
use std::fmt;

#[derive(Debug)]
enum MathError {
    DivideByZero,
}

impl fmt::Display for MathError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MathError::DivideByZero => write!(f, "cannot divide by zero"),
        }
    }
}

fn main() {
    let err = MathError::DivideByZero;
    println!("{}", err);
}

Matching on a Custom Error

Because a custom error is an enum, callers can match on it to respond differently to each specific kind of failure, rather than only having a single generic error message.

Example: Matching on a Custom Error

markup
#[derive(Debug)]
enum MathError {
    DivideByZero,
    NegativeSquareRoot,
}

fn check(n: f64) -> Result<f64, MathError> {
    if n < 0.0 {
        Err(MathError::NegativeSquareRoot)
    } else {
        Ok(n.sqrt())
    }
}

fn main() {
    match check(-4.0) {
        Ok(v) => println!("Root: {}", v),
        Err(MathError::NegativeSquareRoot) => println!("Cannot take root of negative number"),
        Err(MathError::DivideByZero) => println!("Divide by zero"),
    }
}
Common Mistakes
  1. Using String for every error instead of a proper enum, losing the ability for callers to match on specific failure kinds.
  2. Forgetting to implement std::fmt::Display (and often std::error::Error) so the custom error type prints nicely and interoperates with other error-handling code.
  3. Defining a custom error enum but never deriving or implementing Debug, which most error-handling code expects.
Chapter Summary
  • A custom error type is usually an enum with one variant per distinct kind of failure.
  • Implementing std::fmt::Display gives the error type a readable, user-facing message.
  • Implementing std::error::Error lets your custom error interoperate with the wider Rust error-handling ecosystem.
  • Custom error types let callers match on specific failure kinds instead of just a generic message string.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.