050 — Custom Error Types
Tutorial Video
Text description (accessibility)
This video demonstrates the "050 — Custom Error Types" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Custom error types make error handling self-documenting. Key difference from OCaml: 1. **Trait implementation**: Rust requires implementing `Display` and `Error` traits to integrate with the ecosystem. OCaml errors are plain algebraic types — no trait/interface needed, but also no standard display mechanism.
Tutorial
The Problem
Custom error types make error handling self-documenting. Instead of returning String errors (which lose structure) or Box<dyn Error> (which loses type safety), a custom enum provides exhaustive matching, compiler-checked error handling, and rich error messages. This is how Rust's standard library and major crates (serde, tokio, axum) define their errors.
The std::error::Error trait requires implementing Display and optionally source() (for error chaining). The Display implementation provides human-readable messages. Combined with From conversions (example 049), custom error types are the foundation of production Rust error handling.
🎯 Learning Outcomes
std::fmt::Display for human-readable error messagesstd::error::Error to integrate with the Rust ecosystem? and map_err in real functionssource() method for error chaining (wrapping underlying causes)fmt::Display for human-readable messagesstd::error::Error and optionally source() for error chaining with the underlying causeCode Example
#![allow(clippy::all)]
// Placeholder — pending conversionKey Differences
Display and Error traits to integrate with the ecosystem. OCaml errors are plain algebraic types — no trait/interface needed, but also no standard display mechanism.Error::source() returns the underlying cause. OCaml achieves this by embedding the cause in the error variant: type error = IoFailed of exn | ....thiserror::derive(Error), Rust can derive Display and Error from attribute annotations. OCaml has no standard equivalent; writing to_string functions manually is typical.match is checked at compile time — adding a new variant requires handling it everywhere it is matched.enum for error variants:** A custom error type is an enum — each variant represents a different failure mode. This allows callers to pattern-match on specific errors and handle them differently.Display for human-readable messages:** Implementing fmt::Display for the error type provides the human-readable message shown in logs and user interfaces. Debug is for developers; Display is for users.Error trait:** Implementing std::error::Error marks the type as an error type. It requires Debug + Display. The source() method provides error chaining — linking to the underlying cause.type error = InvalidInput | NetworkError of string | ParseError of int * string. Pattern matching handles each case. OCaml doesn't have a standard Error trait — error handling style varies by library.OCaml Approach
OCaml's equivalent: define type validation_error = NegativeAge of int | UnreasonableAge of int | EmptyName. The Display equivalent is a to_string function: let error_to_string = function NegativeAge n -> Printf.sprintf "negative age: %d" n | .... OCaml has no Error trait — errors are just values. The Printexc module handles exception pretty-printing separately.
Full Source
#![allow(clippy::all)]
// Placeholder — pending conversionDeep Comparison
OCaml vs Rust: Custom Error Types
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
NetworkError { message: String, cause: Box<dyn std::error::Error> } variant to your error type and implement source() to return Some(cause.as_ref()). Write a function that wraps an underlying error."expected age 0-150, got 200". Use write!(f, ...) with multiple fields from the variant.ApiError type for a web API with variants for NotFound { resource: String }, Unauthorized, RateLimit { retry_after_secs: u64 }, and Internal(Box<dyn std::error::Error>). Implement Display, Error, and HTTP status code mapping.DbError, ApiError (which wraps DbError), and AppError (which wraps ApiError). Implement From conversions so ? works at each level.fn suggestions(&self) -> Vec<String> method to your custom error type that returns actionable suggestions for each error variant — e.g., for InvalidInput suggest checking the input format.