โข Option
โข Result
โข 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
โข Option
โข Result
โข 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
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`).
| Concept | OCaml | Rust |
|---|---|---|
| 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 error | Manual 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)