🎓 A1Learner
🔍

Rust Wrapping Errors

Wrapping an error means holding a library error inside one variant of a custom error enum. The custom enum gives callers a short list of kinds to match.

Hold the foreign error in a variant

Your enum stays yours. The library error rides inside one variant, kept whole.

#[derive(Debug)]
enum DoubleError {
    Empty,
    Parse(ParseIntError),
}

From makes ? wrap it for you

Write the conversion once. After that, ? wraps a parse error into your Parse variant on its own.

impl From<ParseIntError> for DoubleError {
    fn from(err: ParseIntError) -> DoubleError {
        DoubleError::Parse(err)
    }
}

Your own Display sentence

Each variant gets one clear sentence, in your program's words.

DoubleError::Empty => write!(f, "please give at least one item"),
DoubleError::Parse(_) => write!(f, "the first item is not a number"),

source() hands back the cause

Override source to return the wrapped error. Without this override, the cause stays hidden.

fn source(&self) -> Option<&(dyn Error + 'static)> {
    match self {
        DoubleError::Empty => None,
        DoubleError::Parse(e) => Some(e),
    }
}

The caller prints the chain

Your sentence first, then the cause under it — this is the closing program's printer, fed the bad list.

print(double_first(vec!["tofu", "93"]));

Example

use std::error::Error;
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum DoubleError {
    Empty,
    Parse(ParseIntError),
}
impl fmt::Display for DoubleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            DoubleError::Empty => write!(f, "please give at least one item"),
            DoubleError::Parse(_) => write!(f, "the first item is not a number"),
        }
    }
}
impl Error for DoubleError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            DoubleError::Empty => None,
            DoubleError::Parse(e) => Some(e),
        }
    }
}
impl From<ParseIntError> for DoubleError {
    fn from(err: ParseIntError) -> DoubleError {
        DoubleError::Parse(err)
    }
}
fn double_first(items: Vec<&str>) -> Result<i32, DoubleError> {
    let first = items.first().ok_or(DoubleError::Empty)?;
    let parsed: i32 = first.parse()?;
    Ok(parsed * 2)
}
fn print(result: Result<i32, DoubleError>) {
    match result {
        Ok(n) => println!("first doubled = {}", n),
        Err(e) => {
            println!("Error: {}", e);
            if let Some(source) = e.source() {
                println!("  Caused by: {}", source);
            }
        }
    }
}
fn main() {
    print(double_first(vec!["42", "93"]));
    print(double_first(vec!["tofu", "93"]));
    print(double_first(vec![]));
}

Key points

  • Wrapping costs more code than boxing: a variant, a From impl, Display, and source for each error you wrap

In this example, DoubleError has two variants. Empty is the function's own failure. Parse holds a whole ParseIntError inside it. When parse fails inside double_first, the ? wraps the error for free, because the From impl says how. print shows the result three ways. The good list doubles 42 to 84. The tofu list fails. The message is the function's own sentence. source hands back the inner parse error. It prints as Caused by: invalid digit found in string. The empty list fails with the Empty sentence, and there is no Caused by line, because its source is None.

Note

The payoff is control.

Callers can match your variants and react to each kind.

The original error is still there when they need the detail.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does implementing From<ParseIntError> for your error type give you?