๐Ÿฆ€ 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

049: Error Conversion

Difficulty: 1 Level: Foundations Combine functions with different error types using `From<E>` โ€” so `?` can auto-convert them.

The Problem This Solves

Real applications call functions from multiple sources: your own code, standard library functions, third-party crates. Each returns a different error type. A file function returns `std::io::Error`. A JSON parser returns `serde_json::Error`. Your own validation code returns `MyValidationError`. When you want to use `?` to propagate all of these from a single function, there's a type mismatch โ€” your function returns `Result<T, AppError>`, but you're calling functions that return `Result<T, ParseIntError>`, `Result<T, IoError>`, etc. You need conversions. In Python, this isn't an issue โ€” all exceptions share a common base class and propagate freely. But that also means you have no idea which exceptions a function might raise unless you read every line of code it calls. Rust's `From` trait is the explicit, opt-in version: you decide which error types can convert to your `AppError`, you implement the conversions, and then `?` uses them automatically.

The Intuition

`From<E>` is a trait that says: "my type can be created from `E`." Once you implement `From<ParseError> for AppError`, the `?` operator knows how to convert a `ParseError` into an `AppError` automatically โ€” no manual `.map_err()` needed at each call site. Think of it like Python's exception hierarchy, but opt-in and explicit. In Python, `ValueError` is a `Exception` because of inheritance โ€” implicit. In Rust, `AppError` can be created from `ParseError` because you explicitly wrote `From<ParseError> for AppError` โ€” explicit, composable, documented. The key insight: `?` on a `Result<T, E>` in a function returning `Result<T, AppError>` calls `AppError::from(e)` automatically. If you've implemented `From<E> for AppError`, it just works.

How It Works in Rust

// Your application's unified error type
#[derive(Debug)]
enum AppError {
 Parse(ParseError),
 Math(MathError),
 Io(String),
}

// Tell Rust: ParseError can become AppError
impl From<ParseError> for AppError {
 fn from(e: ParseError) -> Self { AppError::Parse(e) }
}

// Tell Rust: MathError can become AppError
impl From<MathError> for AppError {
 fn from(e: MathError) -> Self { AppError::Math(e) }
}

// These return different error types:
fn parse_positive(s: &str) -> Result<i64, ParseError> { ... }
fn reciprocal(x: i64) -> Result<f64, MathError> { ... }

// Now you can use ? on both โ€” conversions happen automatically!
fn pipeline(s: &str) -> Result<f64, AppError> {
 let n = parse_positive(s)?;   // ParseError โ†’ AppError::Parse via From
 let r = reciprocal(n)?;       // MathError โ†’ AppError::Math via From
 Ok(r * 100.0)
}

// map_err: manual conversion when you don't have From
let result: Result<i64, AppError> =
 "42".parse::<i64>()
     .map_err(|e| AppError::Io(e.to_string()));  // explicit conversion

// Into is the mirror of From โ€” once you implement From, Into is free
let pe = ParseError("bad input".to_string());
let ae: AppError = pe.into();  // calls From<ParseError> for AppError

What This Unlocks

Key Differences

ConceptOCamlRust
Error unificationManual wrapping or polymorphic variants`enum AppError` + `From<E>` impls
Auto-conversionNot built-in, use explicit wrapping`?` calls `From::from()` automatically
Manual conversion`Result.map_error``.map_err(\e\...)`
Symmetric traitN/A`Into<T>` is auto-derived from `From<T>`
Std error types`Stdlib` exceptions are all one typeEach module has own error type; `From` bridges them
Conversion costNone (OCaml GC manages)Zero-cost (Rust move semantics, no allocation)
// Error Conversion โ€” 99 Problems #49
// Convert between error types using From/Into and map_err.

use std::num::ParseIntError;
use std::fmt;

// โ”€โ”€ Error types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#[derive(Debug)]
struct ParseError(String);

#[derive(Debug)]
struct MathError(String);

#[derive(Debug)]
enum AppError {
    Parse(ParseError),
    Math(MathError),
    Io(String),
}

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

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

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::Parse(e) => write!(f, "{}", e),
            AppError::Math(e) => write!(f, "{}", e),
            AppError::Io(s) => write!(f, "IoError: {}", s),
        }
    }
}

// โ”€โ”€ From implementations enable ? operator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

impl From<ParseError> for AppError {
    fn from(e: ParseError) -> Self { AppError::Parse(e) }
}

impl From<MathError> for AppError {
    fn from(e: MathError) -> Self { AppError::Math(e) }
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self {
        AppError::Parse(ParseError(e.to_string()))
    }
}

// โ”€โ”€ Functions returning specific error types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn parse_positive(s: &str) -> Result<i64, ParseError> {
    let n: i64 = s.trim().parse().map_err(|_| ParseError(format!("not an int: {}", s)))?;
    if n > 0 {
        Ok(n)
    } else {
        Err(ParseError(format!("not positive: {}", n)))
    }
}

fn reciprocal(x: i64) -> Result<f64, MathError> {
    if x == 0 {
        Err(MathError("reciprocal of zero".to_string()))
    } else {
        Ok(1.0 / x as f64)
    }
}

/// Combines both error types; From lets ? convert automatically.
fn pipeline(s: &str) -> Result<f64, AppError> {
    let n = parse_positive(s)?;   // ParseError โ†’ AppError via From
    let r = reciprocal(n)?;       // MathError โ†’ AppError via From
    Ok(r * 100.0)
}

fn main() {
    let cases = ["5", "0", "-3", "abc"];
    for s in &cases {
        match pipeline(s) {
            Ok(v) => println!("pipeline({:?}) = {:.4}", s, v),
            Err(e) => println!("pipeline({:?}) = Error: {}", s, e),
        }
    }

    // map_err: manual conversion
    let converted: Result<i64, AppError> = "42".parse::<i64>()
        .map_err(|e| AppError::Io(e.to_string()));
    println!("\nmap_err demo: {:?}", converted);

    // Into: the symmetric view of From
    let pe = ParseError("bad input".to_string());
    let ae: AppError = pe.into();  // calls From<ParseError>
    println!("into AppError: {}", ae);
}

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

    #[test]
    fn test_pipeline_ok() {
        let r = pipeline("4").unwrap();
        assert!((r - 25.0).abs() < 1e-9); // 100 / 4 = 25
    }

    #[test]
    fn test_pipeline_parse_error() {
        assert!(matches!(pipeline("abc"), Err(AppError::Parse(_))));
    }

    #[test]
    fn test_pipeline_math_error() {
        // parse_positive("0") fails with ParseError (not positive), not MathError
        assert!(matches!(pipeline("0"), Err(AppError::Parse(_))));
    }

    #[test]
    fn test_from_parse_int_error() {
        let r: Result<i64, AppError> = "x".parse::<i64>().map_err(AppError::from);
        assert!(matches!(r, Err(AppError::Parse(_))));
    }
}
(* Error Conversion *)
(* OCaml 99 Problems #49 *)

(* Implementation for example 49 *)

(* Tests *)
let () =
  (* Add tests *)
  print_endline "โœ“ OCaml tests passed"