316: std::io::Error Patterns
Tutorial Video
Text description (accessibility)
This video demonstrates the "316: std::io::Error Patterns" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. File I/O, network operations, and process management all produce `std::io::Error`. Key difference from OCaml: 1. **Error classification**: Rust's `ErrorKind` is portable across OSes; OCaml's `Unix.error` closely follows POSIX errno codes.
Tutorial
The Problem
File I/O, network operations, and process management all produce std::io::Error. This error type wraps OS-level errors (errno codes) and classifies them by ErrorKind — enabling portable handling of "file not found", "permission denied", or "connection refused" without dealing with raw OS codes. Understanding io::Error and ErrorKind is essential for writing robust system-level Rust code.
🎯 Learning Outcomes
io::Error::new(kind, message) to create IO errors with descriptive messagesErrorKind for portable, OS-independent error classificationio::Error::kind() to categorize errors in match armsio::Result<T> (alias for Result<T, io::Error>)Code Example
#![allow(clippy::all)]
//! # 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");
}
}Key Differences
ErrorKind is portable across OSes; OCaml's Unix.error closely follows POSIX errno codes.io::Result<T>**: Rust provides io::Result<T> as a type alias for Result<T, io::Error> — a common convenience in I/O-heavy code.e.raw_os_error() retrieves the underlying errno; e.kind() gives the portable classification.io::Error::new(kind, msg) creates custom errors that integrate naturally with the IO error ecosystem.OCaml Approach
OCaml's Unix.error type enumerates POSIX errors, and Unix.Unix_error(code, fn, arg) exceptions carry the OS error code, function name, and argument:
let () =
try
let _ = open_in "nonexistent" in ()
with
| Sys_error msg -> Printf.printf "Error: %s\n" msg
| Unix.Unix_error(Unix.ENOENT, fn, arg) ->
Printf.printf "Not found in %s(%s)\n" fn arg
Full Source
#![allow(clippy::all)]
//! # 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");
}
}#[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");
}
}
Deep Comparison
io-error-handling
See README.md for details.
Exercises
NotFound, create a default file; if PermissionDenied, return a clear user-facing message; otherwise propagate.ErrorKind variants for each validation failure and verify the kinds match expectations.WouldBlock / Interrupted errors but propagates all other errors immediately.