How to add context to errors in Rust

Add context to Rust errors by using the ? operator for propagation or mapping errors to custom types with descriptive messages.

Use the ? operator to propagate errors with context or wrap them in a custom error type that implements Display and Error.

use std::fs::File;
use std::io;

fn read_config() -> Result<String, io::Error> {
    let mut file = File::open("config.txt")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

For custom context, define an error enum and map the underlying error:

use std::fmt;
use std::io;

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    ConfigNotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::ConfigNotFound(path) => write!(f, "Config file not found: {}", path),
        }
    }
}

impl std::error::Error for AppError {}

fn load_config(path: &str) -> Result<String, AppError> {
    std::fs::read_to_string(path)
        .map_err(|e| AppError::ConfigNotFound(format!("{}: {}", path, e)))
}