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

1024: File Operation Errors

Difficulty: Beginner Category: Error Handling Concept: Handling `std::io::Error` from file operations and classifying by `ErrorKind` Key Insight: `io::Error` carries a `kind()` enum that lets you match on specific failure modes (NotFound, PermissionDenied, etc.) โ€” much richer than OCaml's single `Sys_error` string.
// 1024: File Operation Errors
// std::io::Error kinds and handling

use std::fs;
use std::io::{self, Write};
use std::path::Path;

// Approach 1: Basic file operations with io::Error
fn read_file(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn write_file(path: &str, content: &str) -> Result<(), io::Error> {
    fs::write(path, content)
}

// Approach 2: Classifying io::Error by kind
fn classify_io_error(err: &io::Error) -> &'static str {
    match err.kind() {
        io::ErrorKind::NotFound => "file not found",
        io::ErrorKind::PermissionDenied => "permission denied",
        io::ErrorKind::AlreadyExists => "already exists",
        io::ErrorKind::InvalidInput => "invalid input",
        io::ErrorKind::TimedOut => "timed out",
        io::ErrorKind::Interrupted => "interrupted",
        io::ErrorKind::WouldBlock => "would block",
        _ => "other IO error",
    }
}

// Approach 3: Converting io::Error to app-specific error
#[derive(Debug)]
enum FileError {
    NotFound(String),
    PermissionDenied(String),
    Other(String),
}

impl std::fmt::Display for FileError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            FileError::NotFound(p) => write!(f, "file not found: {}", p),
            FileError::PermissionDenied(p) => write!(f, "permission denied: {}", p),
            FileError::Other(msg) => write!(f, "file error: {}", msg),
        }
    }
}

fn read_file_typed(path: &str) -> Result<String, FileError> {
    fs::read_to_string(path).map_err(|e| match e.kind() {
        io::ErrorKind::NotFound => FileError::NotFound(path.into()),
        io::ErrorKind::PermissionDenied => FileError::PermissionDenied(path.into()),
        _ => FileError::Other(e.to_string()),
    })
}

// Safe file operation with existence check
fn read_if_exists(path: &str) -> Result<Option<String>, io::Error> {
    if Path::new(path).exists() {
        fs::read_to_string(path).map(Some)
    } else {
        Ok(None)
    }
}

fn main() {
    match read_file("/nonexistent_file_12345") {
        Ok(content) => println!("Content: {}", content),
        Err(e) => println!("Error: {} (kind: {})", e, classify_io_error(&e)),
    }
    println!("Run `cargo test` to verify all examples.");
}

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

    #[test]
    fn test_read_nonexistent() {
        let err = read_file("/nonexistent_file_12345").unwrap_err();
        assert_eq!(err.kind(), io::ErrorKind::NotFound);
    }

    #[test]
    fn test_classify_not_found() {
        let err = io::Error::new(io::ErrorKind::NotFound, "test");
        assert_eq!(classify_io_error(&err), "file not found");
    }

    #[test]
    fn test_classify_permission() {
        let err = io::Error::new(io::ErrorKind::PermissionDenied, "test");
        assert_eq!(classify_io_error(&err), "permission denied");
    }

    #[test]
    fn test_write_read_roundtrip() {
        let tmp = "/tmp/rust_test_1024.txt";
        write_file(tmp, "hello rust").unwrap();
        let content = read_file(tmp).unwrap();
        assert_eq!(content, "hello rust");
        fs::remove_file(tmp).unwrap();
    }

    #[test]
    fn test_typed_error() {
        let err = read_file_typed("/nonexistent_12345").unwrap_err();
        assert!(matches!(err, FileError::NotFound(_)));
        assert!(err.to_string().contains("not found"));
    }

    #[test]
    fn test_read_if_exists() {
        let result = read_if_exists("/nonexistent_12345").unwrap();
        assert!(result.is_none());

        let tmp = "/tmp/rust_test_1024b.txt";
        fs::write(tmp, "exists").unwrap();
        let result = read_if_exists(tmp).unwrap();
        assert_eq!(result, Some("exists".to_string()));
        fs::remove_file(tmp).unwrap();
    }

    #[test]
    fn test_io_error_display() {
        let err = io::Error::new(io::ErrorKind::NotFound, "missing.txt");
        assert_eq!(err.to_string(), "missing.txt");
    }

    #[test]
    fn test_error_kind_matching() {
        // io::ErrorKind is an enum โ€” exhaustive matching available
        let err = fs::read_to_string("/no_such_file_xyz").unwrap_err();
        match err.kind() {
            io::ErrorKind::NotFound => {} // expected
            other => panic!("unexpected error kind: {:?}", other),
        }
    }
}
(* 1024: File Operation Errors *)
(* Handling file I/O errors in OCaml *)

(* Approach 1: Exception-based file I/O *)
let read_file_exn path =
  try
    let ic = open_in path in
    let content = really_input_string ic (in_channel_length ic) in
    close_in ic;
    content
  with
  | Sys_error msg -> failwith (Printf.sprintf "IO error: %s" msg)

(* Approach 2: Result-based file I/O *)
type io_error =
  | FileNotFound of string
  | PermissionDenied of string
  | OtherIO of string

let read_file path =
  try
    let ic = open_in path in
    let content = really_input_string ic (in_channel_length ic) in
    close_in ic;
    Ok content
  with
  | Sys_error msg when String.length msg > 0 ->
    if String.sub msg 0 (min 2 (String.length msg)) = "No" then
      Error (FileNotFound path)
    else
      Error (OtherIO msg)

let write_file path content =
  try
    let oc = open_out path in
    output_string oc content;
    close_out oc;
    Ok ()
  with Sys_error msg -> Error (OtherIO msg)

(* Approach 3: Safe file operations with cleanup *)
let with_file path f =
  let ic = open_in path in
  Fun.protect ~finally:(fun () -> close_in_noerr ic) (fun () -> f ic)

let test_exception () =
  (* Can't really test file ops without temp files, so test the error path *)
  (try
     let _ = read_file_exn "/nonexistent_file_12345" in
     assert false
   with Failure msg -> assert (String.length msg > 0));
  Printf.printf "  Approach 1 (exception-based): passed\n"

let test_result () =
  (match read_file "/nonexistent_file_12345" with
   | Error _ -> ()
   | Ok _ -> assert false);
  Printf.printf "  Approach 2 (result-based): passed\n"

let test_write_read () =
  let tmp = "/tmp/ocaml_test_1024.txt" in
  assert (write_file tmp "hello" = Ok ());
  (match read_file tmp with
   | Ok content -> assert (content = "hello")
   | Error _ -> assert false);
  Sys.remove tmp;
  Printf.printf "  Write/read round-trip: passed\n"

let () =
  Printf.printf "Testing file errors:\n";
  test_exception ();
  test_result ();
  test_write_read ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

File Operation Errors โ€” Comparison

Core Insight

File operations always fail โ€” the question is how richly you can classify and handle those failures.

OCaml Approach

  • `Sys_error of string` โ€” one exception for all I/O failures
  • Must parse the string to distinguish NotFound vs PermissionDenied
  • `open_in`/`open_out` raise exceptions โ€” need `try/with`
  • Cleanup via `Fun.protect ~finally`

Rust Approach

  • `std::io::Error` with `ErrorKind` enum โ€” structured classification
  • `fs::read_to_string` returns `Result<String, io::Error>`
  • Match on `err.kind()` for specific handling
  • RAII handles cleanup (files close when dropped)

Comparison Table

AspectOCamlRust
Error type`Sys_error of string``io::Error` with `ErrorKind`
ClassificationParse error stringMatch `ErrorKind` enum
File read`open_in` + `really_input_string``fs::read_to_string`
Cleanup`Fun.protect ~finally`RAII / `Drop` trait
Custom errorsWrap in variant`map_err` to app error
Error infoString message onlyKind + message + OS code