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

316: std::io::Error Patterns

Difficulty: 2 Level: Intermediate Work with the standard I/O error type โ€” create, inspect, classify, and wrap.

The Problem This Solves

You're writing filesystem or network code and getting back `std::io::Error`. You need to handle "file not found" differently from "permission denied" differently from "would block" โ€” but raw OS error codes like `ENOENT` (2) are platform-specific and unreadable. You also need to create your own `io::Error` values for custom validation, and wrap existing errors with additional context. `std::io::Error` is the universal error type for anything that touches the OS. It wraps OS error codes with `ErrorKind` โ€” a portable enum that names the common cases. Your code can match on `ErrorKind::NotFound` without caring whether it's Linux, macOS, or Windows. And you can construct `io::Error` values from scratch for custom error conditions that fit the I/O error model. This matters because `io::Result<T>` is used everywhere in the standard library. Understanding how to create and classify `io::Error` values is as fundamental to systems Rust as `Option` and `Result` are to application Rust.

The Intuition

`io::ErrorKind` is a portable OS-error classifier: match on it instead of raw error codes, and construct `io::Error::new(kind, message)` when you need to signal I/O-like errors from your own code.

How It Works in Rust

use std::io::{self, ErrorKind};

// Creating custom io::Error values
fn validate_port(port: u16) -> io::Result<u16> {
 if port == 0 {
     return Err(io::Error::new(ErrorKind::InvalidInput, "port cannot be zero"));
 }
 if port < 1024 {
     return Err(io::Error::new(
         ErrorKind::PermissionDenied,
         format!("port {} requires root", port),
     ));
 }
 Ok(port)
}

// Matching on ErrorKind for portable error handling
fn handle_io_error(e: &io::Error) {
 match e.kind() {
     ErrorKind::NotFound       => eprintln!("File not found"),
     ErrorKind::PermissionDenied => eprintln!("Access denied"),
     ErrorKind::WouldBlock     => eprintln!("Not ready โ€” try again"),
     ErrorKind::InvalidInput   => eprintln!("Invalid: {}", e),
     other => eprintln!("I/O error ({:?}): {}", other, e),
 }
}

// Wrapping an io::Error with context (preserve the kind)
match std::fs::read_to_string("config.toml") {
 Err(e) => {
     let wrapped = io::Error::new(
         e.kind(),  // preserve the original ErrorKind
         format!("loading config: {}", e),
     );
     return Err(wrapped);
 }
 Ok(s) => s,
}

// From OS error code (useful for FFI)
let not_found = io::Error::from_raw_os_error(2);  // ENOENT
assert_eq!(not_found.kind(), ErrorKind::NotFound);
`e.raw_os_error()` returns the original OS code if there is one โ€” useful for debugging but not for matching logic (always prefer `ErrorKind`).

What This Unlocks

Key Differences

ConceptOCamlRust
I/O error type`Unix.error` exceptions`std::io::Error` value
Error classification`Unix.ENOENT`, `Unix.EACCES` etc.`io::ErrorKind` enum โ€” portable
Custom I/O errorManual exception type`io::Error::new(kind, msg)`
OS error code`Unix.error_message``e.raw_os_error()` โ€” use `kind()` for logic
//! # std::io::Error patterns
//!
//! `std::io::Error` wraps OS errors with `ErrorKind` for portable classification.

use std::io::{self, ErrorKind};

/// Validate a port number
pub fn validate_port(port: u16) -> io::Result<u16> {
    if port == 0 {
        return Err(io::Error::new(ErrorKind::InvalidInput, "port cannot be zero"));
    }
    if port < 1024 {
        return Err(io::Error::new(
            ErrorKind::PermissionDenied,
            format!("port {} requires root", port),
        ));
    }
    Ok(port)
}

/// Check path validity
pub fn check_path(path: &str) -> io::Result<()> {
    if path.is_empty() {
        return Err(io::Error::new(ErrorKind::InvalidInput, "path cannot be empty"));
    }
    Ok(())
}

/// Classify io::Error by kind
pub fn classify_error(e: &io::Error) -> &'static str {
    match e.kind() {
        ErrorKind::NotFound => "not found",
        ErrorKind::PermissionDenied => "permission denied",
        ErrorKind::InvalidInput => "invalid input",
        ErrorKind::WouldBlock => "would block",
        _ => "other",
    }
}

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

    #[test]
    fn test_validate_port_zero() {
        let r = validate_port(0);
        assert!(r.is_err());
        assert_eq!(r.unwrap_err().kind(), ErrorKind::InvalidInput);
    }

    #[test]
    fn test_validate_port_privileged() {
        let r = validate_port(80);
        assert_eq!(r.unwrap_err().kind(), ErrorKind::PermissionDenied);
    }

    #[test]
    fn test_validate_port_ok() {
        assert_eq!(validate_port(8080).unwrap(), 8080);
    }

    #[test]
    fn test_check_path_empty() {
        let r = check_path("");
        assert_eq!(r.unwrap_err().kind(), ErrorKind::InvalidInput);
    }

    #[test]
    fn test_classify_error() {
        let e = io::Error::new(ErrorKind::NotFound, "file missing");
        assert_eq!(classify_error(&e), "not found");
    }
}
(* 316. std::io::Error patterns - OCaml *)

let () =
  (* OCaml Unix errors *)
  let try_open path =
    try
      let ic = open_in path in
      let content = input_line ic in
      close_in ic;
      Ok content
    with
    | Sys_error msg -> Error msg
    | End_of_file -> Error "empty file"
  in
  (match try_open "/etc/hostname" with
  | Ok content -> Printf.printf "hostname: %s\n" content
  | Error e -> Printf.printf "Error: %s\n" e);

  (match try_open "/nonexistent/file" with
  | Ok _ -> ()
  | Error e -> Printf.printf "Expected error: %s\n" e);

  (* Creating custom errors *)
  let validate_path path =
    if String.length path = 0 then Error "empty path"
    else if path.[0] <> '/' then Error "path must be absolute"
    else Ok path
  in
  (match validate_path "relative/path" with
  | Ok p -> Printf.printf "Path: %s\n" p
  | Error e -> Printf.printf "Error: %s\n" e)