Module 6 • Lesson 33

Custom Error Types

📚 9 min💻 Free🦀 nixus.pro

Custom Error Types

use std::fmt;
use std::num::ParseIntError;

// Simple custom error
#[derive(Debug)]
enum ConfigError {
    NotFound(String),
    ParseError(String),
    InvalidValue { key: String, value: String, expected: String },
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ConfigError::NotFound(key) =>
                write!(f, "Config key '{}' not found", key),
            ConfigError::ParseError(msg) =>
                write!(f, "Parse error: {}", msg),
            ConfigError::InvalidValue { key, value, expected } =>
                write!(f, "Invalid value '{}' for '{}', expected {}", value, key, expected),
        }
    }
}

impl std::error::Error for ConfigError {}

// Conversions
impl From for ConfigError {
    fn from(e: ParseIntError) -> Self {
        ConfigError::ParseError(e.to_string())
    }
}

fn parse_port(s: &str) -> Result {
    let n: u16 = s.trim().parse()?; // ParseIntError -> ConfigError
    if n < 1024 {
        return Err(ConfigError::InvalidValue {
            key: "port".to_string(),
            value: s.to_string(),
            expected: ">= 1024".to_string(),
        });
    }
    Ok(n)
}

fn main() {
    match parse_port("80") {
        Ok(p) => println!("Port: {}", p),
        Err(e) => println!("Error: {}", e), // Uses Display
    }
    match parse_port("8080") {
        Ok(p) => println!("Port: {}", p),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Using thiserror (Recommended Crate)

// Cargo.toml: thiserror = "1"
use thiserror::Error;

#[derive(Debug, Error)]
enum DatabaseError {
    #[error("Connection failed: {0}")]
    ConnectionFailed(String),

    #[error("Query error on table '{table}': {message}")]
    QueryError { table: String, message: String },

    #[error("Record not found: id={id}")]
    NotFound { id: u64 },

    #[error("IO error")]
    Io(#[from] std::io::Error),
}

fn find_user(id: u64) -> Result {
    if id == 0 {
        return Err(DatabaseError::NotFound { id });
    }
    Ok(format!("user_{}", id))
}

fn main() {
    println!("{}", find_user(0).unwrap_err()); // "Record not found: id=0"
}

🎯 Practice

  1. Create a ValidationError enum with variants for different validation failures
  2. Implement Display and Error traits manually (without thiserror)
  3. Add a From<io::Error> conversion so ? works in functions returning ValidationError

🎉 Key Takeaways