C.W.K.
Stream
Lesson 04 of 05 · published

Custom Error Types

~12 min · errors, custom, from, enum

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

The standard error types take you far, but real programs define their own. A custom error type lets you enumerate exactly the ways your code can fail — and pattern-match on them like any enum.

An error enum

The idiomatic shape is an enum with one variant per failure mode: NotFound, Invalid(String), Io(...). Callers can match on the variant to react differently to each kind of failure — retry on a timeout, report on a parse error, give up on a missing file.

Wiring up ? with From

To let ? convert a lower-level error into your enum, implement From<ThatError> for YourError. Now any function returning Result<_, YourError> can use ? on operations that fail with ThatError, and the conversion happens automatically. This is the concrete payoff of the From trait from the Traits track.

Implement Display and std::error::Error for a 'real' error type. Deriving Debug and implementing Display (a human message) plus the marker trait std::error::Error makes your type a well-behaved citizen — usable with Box<dyn Error>, printable, convertible. It's a little boilerplate, which is exactly what the crates in the next lesson remove.

When to bother

A throwaway script can return Result<_, String> or Box<dyn Error> and move on. A library other people depend on should define a precise error enum, so callers can match and react. The size of the program — and who consumes the errors — decides how much structure is worth it.

Code

A custom error enum with Display + Error·rust
use std::fmt;

#[derive(Debug)]
enum ConfigError {
    Missing,
    Invalid(String),
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ConfigError::Missing => write!(f, "config is missing"),
            ConfigError::Invalid(s) => write!(f, "invalid config: {s}"),
        }
    }
}
impl std::error::Error for ConfigError {}

fn parse_port(s: &str) -> Result<u16, ConfigError> {
    s.trim().parse::<u16>().map_err(|_| ConfigError::Invalid(s.into()))
}

fn main() {
    match parse_port("abc") {
        Ok(p) => println!("port {p}"),
        Err(e) => println!("error: {e}"),
    }
}

External links

Exercise

Define an error enum with three variants for a small file-config loader (NotFound, Empty, Parse(String)). Implement Display and std::error::Error. Write a function returning Result<Config, ConfigError> and match on the variants at the call site. Which variant would you make recoverable-with-default vs fatal?
Hint
NotFound might warrant a default config; Parse usually means the user made a mistake worth reporting. Matching on a precise enum lets the caller make that call per variant — the whole point of a custom error type.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.