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 }))