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

1025: Network Error Classification

Difficulty: Intermediate Category: Error Handling Concept: Classifying network errors by type and implementing retry logic based on error properties Key Insight: Methods on error enums like `is_retryable()` encode domain knowledge directly in the type โ€” the error itself tells you how to handle it.
// 1025: Network Error Classification (Simulated)
// Classifying and handling network-like errors

use std::fmt;

#[derive(Debug)]
enum NetError {
    Timeout { seconds: f64 },
    ConnectionRefused(String),
    DnsResolutionFailed(String),
    TlsError(String),
    HttpError { status: u16, body: String },
}

impl fmt::Display for NetError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            NetError::Timeout { seconds } => write!(f, "timeout after {:.1}s", seconds),
            NetError::ConnectionRefused(host) => write!(f, "connection refused: {}", host),
            NetError::DnsResolutionFailed(host) => write!(f, "DNS failed: {}", host),
            NetError::TlsError(msg) => write!(f, "TLS error: {}", msg),
            NetError::HttpError { status, body } => write!(f, "HTTP {}: {}", status, body),
        }
    }
}
impl std::error::Error for NetError {}

impl NetError {
    fn is_retryable(&self) -> bool {
        match self {
            NetError::Timeout { .. } => true,
            NetError::ConnectionRefused(_) => true,
            NetError::DnsResolutionFailed(_) => false,
            NetError::TlsError(_) => false,
            NetError::HttpError { status, .. } => *status >= 500,
        }
    }

    fn is_client_error(&self) -> bool {
        matches!(self, NetError::HttpError { status, .. } if *status >= 400 && *status < 500)
    }
}

// Simulated network call
fn fetch(url: &str) -> Result<String, NetError> {
    match url {
        "" => Err(NetError::DnsResolutionFailed("empty url".into())),
        "http://timeout" => Err(NetError::Timeout { seconds: 30.0 }),
        "http://refused" => Err(NetError::ConnectionRefused("refused:80".into())),
        "http://500" => Err(NetError::HttpError {
            status: 500,
            body: "Internal Server Error".into(),
        }),
        "http://404" => Err(NetError::HttpError {
            status: 404,
            body: "Not Found".into(),
        }),
        url => Ok(format!("response from {}", url)),
    }
}

// Retry logic
fn fetch_with_retry(url: &str, max_retries: u32) -> Result<String, NetError> {
    let mut last_error = None;
    for attempt in 0..=max_retries {
        match fetch(url) {
            Ok(response) => return Ok(response),
            Err(e) if e.is_retryable() && attempt < max_retries => {
                last_error = Some(e);
                // In real code: sleep with exponential backoff
                continue;
            }
            Err(e) => return Err(e),
        }
    }
    Err(last_error.unwrap())
}

fn main() {
    let urls = &["http://example.com", "http://timeout", "http://404", ""];
    for url in urls {
        match fetch(url) {
            Ok(resp) => println!("OK: {}", resp),
            Err(e) => println!("ERR [retryable={}]: {}", e.is_retryable(), e),
        }
    }
    println!("Run `cargo test` to verify all examples.");
}

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

    #[test]
    fn test_success() {
        assert!(fetch("http://example.com").is_ok());
    }

    #[test]
    fn test_timeout() {
        let err = fetch("http://timeout").unwrap_err();
        assert!(matches!(err, NetError::Timeout { .. }));
        assert!(err.is_retryable());
    }

    #[test]
    fn test_connection_refused() {
        let err = fetch("http://refused").unwrap_err();
        assert!(matches!(err, NetError::ConnectionRefused(_)));
        assert!(err.is_retryable());
    }

    #[test]
    fn test_dns_not_retryable() {
        let err = fetch("").unwrap_err();
        assert!(matches!(err, NetError::DnsResolutionFailed(_)));
        assert!(!err.is_retryable());
    }

    #[test]
    fn test_http_500_retryable() {
        let err = fetch("http://500").unwrap_err();
        assert!(err.is_retryable());
        assert!(!err.is_client_error());
    }

    #[test]
    fn test_http_404_not_retryable() {
        let err = fetch("http://404").unwrap_err();
        assert!(!err.is_retryable());
        assert!(err.is_client_error());
    }

    #[test]
    fn test_retry_success() {
        let result = fetch_with_retry("http://example.com", 3);
        assert!(result.is_ok());
    }

    #[test]
    fn test_retry_exhausted() {
        let result = fetch_with_retry("http://timeout", 2);
        assert!(result.is_err());
    }

    #[test]
    fn test_no_retry_on_client_error() {
        let result = fetch_with_retry("http://404", 3);
        assert!(result.is_err()); // should fail immediately, no retries
    }

    #[test]
    fn test_display() {
        let err = NetError::Timeout { seconds: 5.0 };
        assert_eq!(err.to_string(), "timeout after 5.0s");

        let err = NetError::HttpError { status: 503, body: "Unavailable".into() };
        assert_eq!(err.to_string(), "HTTP 503: Unavailable");
    }
}
(* 1025: Network Error Classification (Simulated) *)
(* Classifying and handling network-like errors *)

type net_error =
  | Timeout of float          (* seconds waited *)
  | ConnectionRefused of string  (* host *)
  | DnsResolutionFailed of string
  | TlsError of string
  | HttpError of int * string    (* status code, body *)

let string_of_net_error = function
  | Timeout secs -> Printf.sprintf "timeout after %.1fs" secs
  | ConnectionRefused host -> Printf.sprintf "connection refused: %s" host
  | DnsResolutionFailed host -> Printf.sprintf "DNS failed: %s" host
  | TlsError msg -> Printf.sprintf "TLS error: %s" msg
  | HttpError (code, body) -> Printf.sprintf "HTTP %d: %s" code body

let is_retryable = function
  | Timeout _ -> true
  | ConnectionRefused _ -> true
  | DnsResolutionFailed _ -> false  (* unlikely to change *)
  | TlsError _ -> false
  | HttpError (code, _) -> code >= 500  (* server errors are retryable *)

(* Simulated network call *)
let fetch url =
  if String.length url = 0 then Error (DnsResolutionFailed "empty url")
  else if url = "http://timeout" then Error (Timeout 30.0)
  else if url = "http://refused" then Error (ConnectionRefused "refused:80")
  else if url = "http://500" then Error (HttpError (500, "Internal Server Error"))
  else if url = "http://404" then Error (HttpError (404, "Not Found"))
  else Ok (Printf.sprintf "response from %s" url)

(* Retry logic *)
let rec fetch_with_retry url retries =
  match fetch url with
  | Ok _ as result -> result
  | Error e when is_retryable e && retries > 0 ->
    fetch_with_retry url (retries - 1)
  | Error _ as result -> result

let test_errors () =
  assert (fetch "http://example.com" = Ok "response from http://example.com");
  (match fetch "" with
   | Error (DnsResolutionFailed _) -> ()
   | _ -> assert false);
  (match fetch "http://timeout" with
   | Error (Timeout _) -> ()
   | _ -> assert false);
  Printf.printf "  Error classification: passed\n"

let test_retryable () =
  assert (is_retryable (Timeout 30.0));
  assert (is_retryable (ConnectionRefused "host"));
  assert (not (is_retryable (DnsResolutionFailed "host")));
  assert (is_retryable (HttpError (503, "Unavailable")));
  assert (not (is_retryable (HttpError (404, "Not Found"))));
  Printf.printf "  Retryable classification: passed\n"

let test_retry () =
  (* This will retry but still fail (simulated always-timeout) *)
  (match fetch_with_retry "http://timeout" 3 with
   | Error (Timeout _) -> ()
   | _ -> assert false);
  (* Success doesn't need retry *)
  assert (fetch_with_retry "http://example.com" 3
          = Ok "response from http://example.com");
  Printf.printf "  Retry logic: passed\n"

let () =
  Printf.printf "Testing network errors:\n";
  test_errors ();
  test_retryable ();
  test_retry ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Network Error Classification โ€” Comparison

Core Insight

Network errors need classification (retryable? client error? transient?) for proper handling. Both languages use pattern matching, but Rust's methods on enums keep the logic co-located with the type.

OCaml Approach

  • Variant type with all error kinds
  • Standalone `is_retryable` function matches on variants
  • Retry logic uses recursive function with decrementing counter
  • `string_of_*` functions for display

Rust Approach

  • Enum with methods: `impl NetError { fn is_retryable(&self) -> bool }`
  • Pattern matching with guards: `Err(e) if e.is_retryable()`
  • Retry loop with attempt counter
  • `Display` trait for formatting

Comparison Table

AspectOCamlRust
Error typeVariant typeEnum
ClassificationStandalone functionMethod on enum
Retry guard`when is_retryable e``if e.is_retryable()`
Structured data`HttpError of int * string``HttpError { status, body }`
Display`string_of_net_error``impl Display`
Methods on errorNot idiomaticVery idiomatic