Essential Standard Traits

Error

The std::error::Error trait (also available in core::error::Error since Rust 1.81) is the standard interface for error values, combining human-readable formatting (Display), developer diagnostics (Debug), and causal error chaining (source).

Trait Definition

pub trait Error: Debug + Display {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}

Because Error has Debug and Display as supertraits:

  1. Every Error type must implement Debug (typically via #[derive(Debug)]) and Display.
  2. If the error does not wrap a lower-level cause, the impl Error for MyError {} block can be completely empty because source() defaults to None.

Implementing Error with Causal Chaining (source)

Overriding source(&self) exposes the underlying lower-level error (&(dyn Error + 'static)) so error reporters and logging frameworks can walk the entire causal chain:

use std::error::Error;
use std::fmt;
use std::num::ParseIntError;

#[derive(Debug)]
struct ConfigError {
    key: &'static str,
    cause: ParseIntError,
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "invalid integer for configuration key '{}'", self.key)
    }
}

impl Error for ConfigError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.cause)
    }
}

fn parse_port(raw: &str) -> Result<u16, ConfigError> {
    raw.parse::<u16>().map_err(|err| ConfigError {
        key: "PORT",
        cause: err,
    })
}

fn main() {
    let err = parse_port("not_a_number").unwrap_err();
    println!("Top-level error: {err}");

    let mut current: Option<&dyn Error> = err.source();
    while let Some(cause) = current {
        println!("  Caused by:     {cause}");
        current = cause.source();
    }
}
Top-level error: invalid integer for configuration key 'PORT'
  Caused by:     invalid digit found in string

Type-Erased Errors with Box<dyn Error>

The standard library provides a blanket conversion impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>. Functions that can fail with several different error types (such as main(), CLI scripts, or test harnesses) can return Result<T, Box<dyn Error>> and use the ? operator on any error type that implements std::error::Error:

use std::error::Error;

fn parse_and_access(items: &[&str], index_str: &str) -> Result<i32, Box<dyn Error>> {
    let idx: usize = index_str.parse()?; // Converts ParseIntError -> Box<dyn Error>
    let raw = items.get(idx).ok_or("index out of bounds")?; // Converts &str -> Box<dyn Error>
    let val: i32 = raw.parse()?;
    Ok(val)
}

fn main() {
    let data = ["10", "25", "bad"];
    println!("Valid:   {:?}", parse_and_access(&data, "1"));
    println!("Missing: {:?}", parse_and_access(&data, "9"));
}
Valid:   Ok(25)
Missing: Err("index out of bounds")