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

1021: Error Propagation Depth

Difficulty: Intermediate Category: Error Handling Concept: Multi-layer error propagation through 5 levels using `?` Key Insight: With a shared error type, `?` propagates errors through any number of layers with zero syntactic overhead โ€” each layer is just one `?` away from its caller.
// 1021: Error Propagation Depth
// 5-level error propagation with ?

use std::fmt;

#[derive(Debug, PartialEq)]
enum AppError {
    ConfigMissing(String),
    ParseFailed(String),
    ValidationFailed(String),
    ServiceUnavailable(String),
    Timeout,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::ConfigMissing(s) => write!(f, "config missing: {}", s),
            AppError::ParseFailed(s) => write!(f, "parse failed: {}", s),
            AppError::ValidationFailed(s) => write!(f, "validation: {}", s),
            AppError::ServiceUnavailable(s) => write!(f, "service unavailable: {}", s),
            AppError::Timeout => write!(f, "timeout"),
        }
    }
}
impl std::error::Error for AppError {}

// Level 1: Config layer
fn read_config(key: &str) -> Result<String, AppError> {
    if key == "missing" {
        Err(AppError::ConfigMissing(key.into()))
    } else {
        Ok("8080".into())
    }
}

// Level 2: Parse layer
fn parse_port(s: &str) -> Result<u16, AppError> {
    s.parse::<u16>()
        .map_err(|_| AppError::ParseFailed(s.into()))
}

// Level 3: Validation layer
fn validate_port(port: u16) -> Result<u16, AppError> {
    if port == 0 {
        Err(AppError::ValidationFailed(format!("port {} invalid", port)))
    } else {
        Ok(port)
    }
}

// Level 4: Connection layer
fn connect(_host: &str, port: u16) -> Result<String, AppError> {
    if port == 9999 {
        Err(AppError::ServiceUnavailable("connection refused".into()))
    } else {
        Ok(format!("connected:{}", port))
    }
}

// Level 5: Application layer โ€” chains all with ?
fn start_service(key: &str, host: &str) -> Result<String, AppError> {
    let raw = read_config(key)?;         // Level 1
    let port = parse_port(&raw)?;        // Level 2
    let valid = validate_port(port)?;    // Level 3
    let conn = connect(host, valid)?;    // Level 4
    Ok(conn)                             // Level 5 success
}

fn main() {
    let cases = vec![
        ("port", "localhost"),
        ("missing", "localhost"),
    ];
    for (key, host) in cases {
        match start_service(key, host) {
            Ok(conn) => println!("Success: {}", conn),
            Err(e) => println!("Error: {}", e),
        }
    }
    println!("Run `cargo test` to verify all examples.");
}

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

    #[test]
    fn test_full_success() {
        assert_eq!(start_service("port", "localhost"), Ok("connected:8080".into()));
    }

    #[test]
    fn test_level1_config_error() {
        let err = start_service("missing", "localhost").unwrap_err();
        assert!(matches!(err, AppError::ConfigMissing(_)));
    }

    #[test]
    fn test_level2_parse_error() {
        // parse_port directly
        let err = parse_port("abc").unwrap_err();
        assert!(matches!(err, AppError::ParseFailed(_)));
    }

    #[test]
    fn test_level3_validation_error() {
        let err = validate_port(0).unwrap_err();
        assert!(matches!(err, AppError::ValidationFailed(_)));
    }

    #[test]
    fn test_level4_connection_error() {
        let err = connect("host", 9999).unwrap_err();
        assert!(matches!(err, AppError::ServiceUnavailable(_)));
    }

    #[test]
    fn test_error_display() {
        let err = AppError::ConfigMissing("db_url".into());
        assert_eq!(err.to_string(), "config missing: db_url");

        let err = AppError::Timeout;
        assert_eq!(err.to_string(), "timeout");
    }

    #[test]
    fn test_question_mark_propagates_correctly() {
        // Each ? passes the error through unchanged
        fn layer_test() -> Result<(), AppError> {
            let _ = read_config("missing")?;
            Ok(())
        }
        assert!(matches!(layer_test(), Err(AppError::ConfigMissing(_))));
    }

    #[test]
    fn test_all_layers_independent() {
        assert!(read_config("ok").is_ok());
        assert!(parse_port("8080").is_ok());
        assert!(validate_port(80).is_ok());
        assert!(connect("localhost", 80).is_ok());
    }
}
(* 1021: Error Propagation Depth *)
(* 5-level error propagation through layers *)

type app_error =
  | ConfigMissing of string
  | ParseFailed of string
  | ValidationFailed of string
  | ServiceUnavailable of string
  | Timeout

let string_of_error = function
  | ConfigMissing s -> Printf.sprintf "config missing: %s" s
  | ParseFailed s -> Printf.sprintf "parse failed: %s" s
  | ValidationFailed s -> Printf.sprintf "validation: %s" s
  | ServiceUnavailable s -> Printf.sprintf "service unavailable: %s" s
  | Timeout -> "timeout"

let ( let* ) = Result.bind

(* Level 1: Config layer *)
let read_config key =
  if key = "missing" then Error (ConfigMissing key)
  else Ok "8080"

(* Level 2: Parse layer *)
let parse_port s =
  match int_of_string_opt s with
  | None -> Error (ParseFailed s)
  | Some n -> Ok n

(* Level 3: Validation layer *)
let validate_port port =
  if port < 1 || port > 65535 then
    Error (ValidationFailed (Printf.sprintf "port %d out of range" port))
  else Ok port

(* Level 4: Connection layer *)
let connect _host port =
  if port = 9999 then Error (ServiceUnavailable "connection refused")
  else Ok (Printf.sprintf "connected:%d" port)

(* Level 5: Application layer โ€” chains all 4 *)
let start_service key host =
  let* raw = read_config key in
  let* port = parse_port raw in
  let* valid_port = validate_port port in
  let* conn = connect host valid_port in
  Ok conn

let test_success () =
  assert (start_service "port" "localhost" = Ok "connected:8080");
  Printf.printf "  Success path: passed\n"

let test_config_error () =
  (match start_service "missing" "localhost" with
   | Error (ConfigMissing _) -> ()
   | _ -> assert false);
  Printf.printf "  Config error (level 1): passed\n"

let test_validation_error () =
  (* We'd need a way to make parse return out-of-range...
     Let's test validate directly *)
  assert (validate_port 0 |> Result.is_error);
  assert (validate_port 70000 |> Result.is_error);
  assert (validate_port 8080 = Ok 8080);
  Printf.printf "  Validation error (level 3): passed\n"

let test_all_levels () =
  (* Each level can fail independently *)
  let results = [
    start_service "missing" "localhost";  (* level 1 fail *)
    start_service "port" "localhost";     (* success *)
  ] in
  assert (List.length (List.filter Result.is_ok results) = 1);
  assert (List.length (List.filter Result.is_error results) = 1);
  Printf.printf "  All levels: passed\n"

let () =
  Printf.printf "Testing 5-level error propagation:\n";
  test_success ();
  test_config_error ();
  test_validation_error ();
  test_all_levels ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Error Propagation Depth โ€” Comparison

Core Insight

Deep call stacks need error propagation that scales. Both `let*` (OCaml) and `?` (Rust) keep the code flat regardless of depth.

OCaml Approach

  • `let*` chains keep code linear through any number of layers
  • Single error type shared across layers
  • Each `let*` is one potential early exit point
  • Without `let*`: deeply nested match expressions

Rust Approach

  • `?` on each fallible call โ€” one character per layer
  • Shared error enum or `From` impls for automatic conversion
  • Each `?` is an early-return point
  • Without `?`: deeply nested match or try! macro

Comparison Table

AspectOCaml `let`Rust `?`
Syntax per layer`let x = f in``let x = f?;`
Depth scalingLinearLinear
Error typeMust match or wrap`From` auto-converts
Without sugarNested matchNested match
Readability at 5 levelsGoodGood
PerformanceZero-costZero-cost