ExamplesBy LevelBy TopicLearning Paths
316 Intermediate

316: std::io::Error Patterns

Functional Programming

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

  • • Use io::Error::new(kind, message) to create IO errors with descriptive messages
  • • Match on ErrorKind for portable, OS-independent error classification
  • • Use io::Error::kind() to categorize errors in match arms
  • • Implement functions returning io::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

  • Error classification: Rust's 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.
  • OS error access: e.raw_os_error() retrieves the underlying errno; e.kind() gives the portable classification.
  • Custom IO errors: 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");
        }
    }
    ✓ Tests Rust test suite
    #[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

  • Write a function that opens a file and classifies the error: if NotFound, create a default file; if PermissionDenied, return a clear user-facing message; otherwise propagate.
  • Implement a port validator that returns specific ErrorKind variants for each validation failure and verify the kinds match expectations.
  • Write a retry loop that retries an IO operation on WouldBlock / Interrupted errors but propagates all other errors immediately.
  • Open Source Repos