049 — Error Conversion
Tutorial Video
Text description (accessibility)
This video demonstrates the "049 — Error Conversion" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Real applications deal with multiple error types from different libraries: `std::io::Error` from file I/O, `serde_json::Error` from JSON parsing, `reqwest::Error` from HTTP — all in the same function. Key difference from OCaml: 1. **Automatic vs explicit**: Rust's `From` + `?` converts automatically at the `?` site. OCaml requires explicit `Result.map_error` or wrapping helper functions at each error site.
Tutorial
The Problem
Real applications deal with multiple error types from different libraries: std::io::Error from file I/O, serde_json::Error from JSON parsing, reqwest::Error from HTTP — all in the same function. To use ? with mixed error types, you need a unified error type and From implementations for each source error.
Error conversion is where Rust's error handling design becomes concrete. The From trait is the mechanism: impl From<IoError> for AppError { ... } enables automatic conversion via ?. This is analogous to OCaml's polymorphic variant unification — combining multiple error cases into a single type.
🎯 Learning Outcomes
From<SourceError> for a unified AppError type? with multiple different error types in the same function via From conversionmap_err for one-off error conversions without defining FromFrom is clean but requires boilerplate; Box<dyn Error> is quick but less typedthiserror crate pattern for reducing boilerplateFrom<SpecificError> for AppError to enable automatic ?-based conversionBox<dyn std::error::Error> as a catch-all for functions that combine multiple error typesCode Example
#![allow(clippy::all)]
// Placeholder — pending conversionKey Differences
From + ? converts automatically at the ? site. OCaml requires explicit Result.map_error or wrapping helper functions at each error site.thiserror crate**: The thiserror crate generates From implementations via #[from] attribute on enum fields. OCaml has no equivalent; Dune macros or PPX would be needed for comparable automation.Box<dyn Error>**: Rust's Box<dyn std::error::Error> accepts any error type without explicit conversion — useful for prototyping. OCaml's equivalent is catching exceptions or using string as error type.From implementations make error type conversions checked at compile time. Box<dyn Error> sacrifices this for convenience.From trait enables ?:** When ? converts from error type A to error type B, it uses B::from(a). Implementing From<A> for B makes this automatic. This is why library error types implement From for all constituent error types.Box<dyn Error> for type erasure:** Returning Box<dyn Error> accepts any error type — the concrete type is erased. Useful in applications but poor for libraries (callers can't pattern-match on the error).thiserror and anyhow:** The thiserror crate automates From and Display implementations. The anyhow crate uses Box<dyn Error> with added context. Both are idiomatic in real Rust code.?:** OCaml's error conversion is always manual — match and wrap explicitly, or use a ppx for syntactic sugar.OCaml Approach
OCaml's approach uses variant types: type app_error = Io of string | Parse of string. Helper constructors wrap each error: let wrap_io r = Result.map_error (fun e -> Io (Printexc.to_string e)) r. Chaining: let* data = wrap_io (read_file path) in let* n = Result.map_error (fun e -> Parse e) (parse_int data) in Ok n. There is no automatic From-style conversion; wrapping is always explicit.
Full Source
#![allow(clippy::all)]
// Placeholder — pending conversionDeep Comparison
OCaml vs Rust: Error Conversion
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
AppError with three variants for IoError, ParseIntError, and a custom DomainError. Implement From for all three and write a function using all three error types.thiserror pattern**: Without using the thiserror crate, manually write what #[derive(thiserror::Error)] would generate for your AppError enum. Include Display and std::error::Error impls.DatabaseError, AuthError, ValidationError all converting into ApiError. Draw the conversion graph.thiserror macro**: Rewrite a custom error type using the thiserror crate's #[derive(Error)] macro. Compare the amount of boilerplate with the manual implementation.with_context<T, E: Display>(result: Result<T, E>, context: &str) -> Result<T, String> that wraps an error with additional context, producing a String error that includes both the context and original message.