Option<T> and Result<T, E> in Depth

map_err()

The .map_err(op) combinator on Result<T, E> transforms an Err(E) value into Err(F) by applying an FnOnce(E) -> F closure, leaving an Ok(T) value untouched.

Signature

impl<T, E> Result<T, E> {
    pub fn map_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F>;
}
Result<T, E>
  ├── Ok(v)  ── .map_err(op) ──> Ok(v)        (`op` is NOT called; zero overhead on happy path)
  └── Err(e) ── .map_err(op) ──> Err(op(e))   (`op` converts `E` -> `F`)

Because op is only invoked when the result is Err(e), formatting error context strings inside .map_err(|e| format!(...)) has zero runtime cost on the Ok path!

Adapting Foreign Errors and Attaching Context Before ?

When calling a function whose error type E does not implement From<E> for your function's return error type (or when two operations return the same error type std::io::Error and you want to record which file or step failed), chain .map_err(...) right before ?:

use std::num::ParseIntError;

#[derive(Debug)]
struct FieldParseError {
    field: &'static str,
    raw_value: String,
    source: ParseIntError,
}

fn parse_field(field: &'static str, raw: &str) -> Result<u32, FieldParseError> {
    raw.parse::<u32>().map_err(|err| FieldParseError {
        field,
        raw_value: raw.to_owned(),
        source: err,
    })
}

fn parse_dimensions(width_str: &str, height_str: &str) -> Result<(u32, u32), FieldParseError> {
    let w = parse_field("width", width_str)?;
    let h = parse_field("height", height_str)?;
    Ok((w, h))
}

fn main() {
    println!("Valid:   {:?}", parse_dimensions("1920", "1080"));
    println!("Invalid: {:?}", parse_dimensions("1920", "bad_px"));
}
Valid:   Ok((1920, 1080))
Invalid: Err(FieldParseError { field: "height", raw_value: "bad_px", source: ParseIntError { kind: InvalidDigit } })

Passing Enum Variant Constructors Directly to .map_err()

Because tuple-style enum variants (AppError::Io, AppError::Parse) are functions of type fn(InnerError) -> AppError, you can pass the variant name directly to .map_err(AppError::Parse) without writing a closure:

use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    InvalidTimeout(ParseIntError),
    InvalidRetries(ParseIntError),
}

fn load_settings(timeout: &str, retries: &str) -> Result<(u64, u32), AppError> {
    // Disambiguates two `ParseIntError` sources using tuple variant constructors:
    let t = timeout.parse::<u64>().map_err(AppError::InvalidTimeout)?;
    let r = retries.parse::<u32>().map_err(AppError::InvalidRetries)?;
    Ok((t, r))
}

fn main() {
    println!("{:?}", load_settings("5000", "three"));
}
Err(InvalidRetries(ParseIntError { kind: InvalidDigit }))