🦀 Functional Rust
🎬 Error Handling in Rust Option, Result, the ? operator, and combinators.
📝 Text version (for readers / accessibility)

• Option represents a value that may or may not exist — Some(value) or None

• Result represents success (Ok) or failure (Err) — no exceptions needed

• The ? operator propagates errors up the call stack concisely

• Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations

• The compiler forces you to handle every error case — no silent failures

298: The anyhow Pattern — Boxed Errors

Difficulty: 3 Level: Advanced Box any error type into a single container — trade type precision for ergonomics.

The Problem This Solves

You're writing a CLI or application binary. It calls ten different libraries, each with its own error type. Writing a unified enum with `From` impls for all of them is theoretically correct — but also twenty minutes of boilerplate for code you'll never match on. You just want to propagate the error, log it, and exit. The type system is fighting you. `Box<dyn Error + Send + Sync>` solves this. Any type that implements `Error` can be boxed into it. The `?` operator will do the boxing automatically — no `From` impl needed. You lose the ability to match on specific error variants, but for application code that's often the right trade-off: you don't want to handle a parse error differently from a network error; you want to log both and stop. The `anyhow` crate packages this pattern with a `Result<T>` type alias, a `.context()` method for adding human-readable context, and a pretty error reporter. This example shows the same pattern using only `std` — so you understand what `anyhow` is actually doing under the hood.

The Intuition

`Box<dyn Error + Send + Sync>` is a universal error container: any error that implements `Error` can go in, `?` does the boxing, and you get a clean propagation path without writing a single `From` impl.

How It Works in Rust

// Type alias — this is essentially what anyhow::Result<T> is
type AnyResult<T> = Result<T, Box<dyn Error + Send + Sync>>;

fn parse_port(s: &str) -> AnyResult<u16> {
 let n: u16 = s.parse()?;  // ParseIntError gets boxed automatically — no From impl needed
 if n == 0 { return Err("port cannot be zero".into()); }  // &str -> Box<dyn Error> via .into()
 Ok(n)
}

// Adding context: wrap the box in another box with a message
fn load_config(port_str: &str) -> AnyResult<String> {
 let port = parse_port(port_str)
     .map_err(|e| format!("invalid port: {}", e))?;  // contextual message wraps the box
 Ok(format!("localhost:{}", port))
}

// main() can return Box<dyn Error> too — Rust prints it on failure
fn main() -> Result<(), Box<dyn Error>> {
 let addr = load_config("8080")?;
 println!("{}", addr);
 Ok(())
}
The `Send + Sync` bounds matter: without them, you can't send the error across threads, which kills async code. Always use `Box<dyn Error + Send + Sync>`.

What This Unlocks

Key Differences

ConceptOCamlRust
Untyped error`exn` — exceptions are polymorphic by default`Box<dyn Error>` — explicit type erasure
Any errorRaise any exceptionAny type implementing `Error` can be boxed
ContextWrap in new exceptionWrap with `map_err` or `.context()` extension
Library vs appSame either wayLibrary: typed enum; App: `Box<dyn Error>`
Matching on variantsPattern match exceptionsNot possible after boxing — use typed errors for libraries
//! # anyhow-style Boxed Errors
//!
//! `Box<dyn Error + Send + Sync>` is a universal error container — the anyhow pattern.

use std::error::Error;
use std::fmt;

/// Type alias for ergonomics (what anyhow::Result is essentially)
pub type AnyResult<T> = Result<T, Box<dyn Error + Send + Sync>>;

/// A simple context wrapper
#[derive(Debug)]
struct WithContext {
    context: String,
    source: Box<dyn Error + Send + Sync>,
}

impl fmt::Display for WithContext {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.context)
    }
}

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

/// Extension trait for adding context (like anyhow's .context())
pub trait ResultExt<T> {
    fn context(self, msg: &str) -> AnyResult<T>;
}

impl<T, E: Error + Send + Sync + 'static> ResultExt<T> for Result<T, E> {
    fn context(self, msg: &str) -> AnyResult<T> {
        self.map_err(|e| {
            Box::new(WithContext {
                context: msg.to_string(),
                source: Box::new(e),
            }) as Box<dyn Error + Send + Sync>
        })
    }
}

/// Parse port number
pub fn parse_port(s: &str) -> AnyResult<u16> {
    let n: u16 = s.parse()?; // ? boxes any error
    if n == 0 {
        return Err("port cannot be zero".into()); // .into() on &str!
    }
    Ok(n)
}

/// Load configuration
pub fn load_config(port_str: &str, host: &str) -> AnyResult<String> {
    let port = parse_port(port_str).map_err(|e| {
        Box::new(WithContext {
            context: "invalid port number".to_string(),
            source: e,
        }) as Box<dyn Error + Send + Sync>
    })?;
    if host.is_empty() {
        return Err("empty hostname".into());
    }
    Ok(format!("{}:{}", host, port))
}

/// String literal as error
pub fn require_non_empty(s: &str) -> AnyResult<&str> {
    if s.is_empty() {
        Err("value cannot be empty".into())
    } else {
        Ok(s)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_load_config_ok() {
        let result = load_config("8080", "localhost");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "localhost:8080");
    }

    #[test]
    fn test_parse_port_bad() {
        assert!(parse_port("abc").is_err());
    }

    #[test]
    fn test_empty_host() {
        assert!(load_config("8080", "").is_err());
    }

    #[test]
    fn test_port_zero() {
        assert!(parse_port("0").is_err());
    }

    #[test]
    fn test_string_literal_error() {
        let result = require_non_empty("");
        assert!(result.is_err());
    }

    #[test]
    fn test_context_preserves_source() {
        let pe: Result<u16, std::num::ParseIntError> = "abc".parse();
        let result = pe.context("parsing port");
        assert!(result.is_err());
        let e = result.unwrap_err();
        assert!(e.source().is_some());
    }
}
(* 298. anyhow-style boxed errors - OCaml *)
(* OCaml: exceptions serve as anyhow-style dynamic errors *)

exception AnyError of string * exn option

let anyhow msg = Error (AnyError (msg, None))
let context msg = function
  | Error e -> Error (AnyError (msg, Some e))
  | Ok _ as r -> r

let () =
  let parse s =
    match int_of_string_opt s with
    | Some n -> Ok n
    | None -> anyhow ("failed to parse: " ^ s)
  in
  let result =
    parse "abc"
    |> context "while reading config"
  in
  (match result with
  | Ok n -> Printf.printf "Value: %d\n" n
  | Error (AnyError (msg, Some (AnyError (cause, _)))) ->
    Printf.printf "Error: %s\n  Caused by: %s\n" msg cause
  | Error (AnyError (msg, _)) -> Printf.printf "Error: %s\n" msg
  | _ -> ())