โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
// 1006: Multiple Error Types
// Unifying multiple error types: Box<dyn Error> vs enum approach
use std::fmt;
use std::num::ParseIntError;
// Individual error types
#[derive(Debug)]
struct IoError(String);
impl fmt::Display for IoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "IO error: {}", self.0)
}
}
impl std::error::Error for IoError {}
#[derive(Debug)]
struct NetError(String);
impl fmt::Display for NetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "network error: {}", self.0)
}
}
impl std::error::Error for NetError {}
// Approach 1: Box<dyn Error> โ quick and flexible
fn do_io_boxed() -> Result<String, Box<dyn std::error::Error>> {
Err(Box::new(IoError("file not found".into())))
}
fn do_parse_boxed(s: &str) -> Result<i64, Box<dyn std::error::Error>> {
let n: i64 = s.parse()?; // ParseIntError auto-boxed
Ok(n)
}
fn do_net_boxed() -> Result<String, Box<dyn std::error::Error>> {
Err(Box::new(NetError("timeout".into())))
}
fn process_boxed() -> Result<i64, Box<dyn std::error::Error>> {
let data = do_io_boxed().or_else(|_| Ok::<_, Box<dyn std::error::Error>>("42".into()))?;
let parsed = do_parse_boxed(&data)?;
let _response = do_net_boxed().or_else(|_| Ok::<String, Box<dyn std::error::Error>>("ok".into()))?;
Ok(parsed)
}
// Approach 2: Typed enum โ exhaustive matching
#[derive(Debug)]
enum AppError {
Io(IoError),
Parse(ParseIntError),
Net(NetError),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "{}", e),
AppError::Parse(e) => write!(f, "parse: {}", e),
AppError::Net(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for AppError {}
impl From<IoError> for AppError {
fn from(e: IoError) -> Self { AppError::Io(e) }
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
}
impl From<NetError> for AppError {
fn from(e: NetError) -> Self { AppError::Net(e) }
}
fn do_io_typed() -> Result<String, IoError> {
Ok("42".into())
}
fn do_parse_typed(s: &str) -> Result<i64, ParseIntError> {
s.parse()
}
fn process_typed() -> Result<i64, AppError> {
let data = do_io_typed()?; // IoError -> AppError
let parsed = do_parse_typed(&data)?; // ParseIntError -> AppError
Ok(parsed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boxed_error() {
let result = process_boxed();
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_boxed_io_error() {
let err = do_io_boxed().unwrap_err();
assert!(err.to_string().contains("IO error"));
}
#[test]
fn test_typed_success() {
assert_eq!(process_typed().unwrap(), 42);
}
#[test]
fn test_typed_pattern_match() {
let err: AppError = IoError("test".into()).into();
assert!(matches!(err, AppError::Io(_)));
let err: AppError = "abc".parse::<i64>().unwrap_err().into();
assert!(matches!(err, AppError::Parse(_)));
}
#[test]
fn test_boxed_parse_error() {
let result = do_parse_boxed("not_a_number");
assert!(result.is_err());
}
#[test]
fn test_display_format() {
let err = AppError::Net(NetError("timeout".into()));
assert_eq!(err.to_string(), "network error: timeout");
}
}
(* 1006: Multiple Error Types *)
(* Unifying multiple error types into one *)
type io_error = FileNotFound | ReadError of string
type parse_error = BadFormat | Overflow
type net_error = Timeout | ConnectionRefused
(* Approach 1: Unified variant type (like Rust enum) *)
type app_error =
| Io of io_error
| Parse of parse_error
| Net of net_error
let do_io () = Error (Io FileNotFound)
let do_parse () = Error (Parse BadFormat)
let do_net () = Error (Net Timeout)
let process_all () =
match do_io () with
| Error _ as e -> e
| Ok data ->
match do_parse () with
| Error _ as e -> e
| Ok parsed ->
match do_net () with
| Error _ as e -> e
| Ok _ -> Ok (data, parsed)
(* Approach 2: Polymorphic variants โ open, extensible *)
let do_io_poly () : (string, [> `FileNotFound | `ReadError of string]) result =
Error `FileNotFound
let do_parse_poly () : (int, [> `BadFormat | `Overflow]) result =
Ok 42
let process_poly () =
match do_io_poly () with
| Error _ as e -> e
| Ok _data ->
match do_parse_poly () with
| Error _ as e -> e
| Ok v -> Ok v
let test_unified () =
(match do_io () with
| Error (Io FileNotFound) -> ()
| _ -> assert false);
(match do_parse () with
| Error (Parse BadFormat) -> ()
| _ -> assert false);
(match do_net () with
| Error (Net Timeout) -> ()
| _ -> assert false);
Printf.printf " Approach 1 (unified variant): passed\n"
let test_poly () =
(match do_io_poly () with
| Error `FileNotFound -> ()
| _ -> assert false);
assert (do_parse_poly () = Ok 42);
Printf.printf " Approach 2 (polymorphic variants): passed\n"
let () =
Printf.printf "Testing multiple error types:\n";
test_unified ();
test_poly ();
Printf.printf "โ All tests passed\n"
| Aspect | OCaml Variant | OCaml Poly Variant | Rust `Box<dyn>` | Rust Enum |
|---|---|---|---|---|
| Setup cost | Medium | Low | Low | Medium |
| Pattern matching | Yes | Partial | No (need downcast) | Yes, exhaustive |
| Extensibility | Closed | Open | Open | Closed |
| Performance | Zero-cost | Zero-cost | Heap allocation | Zero-cost |
| Best for | Libraries | Prototyping | Scripts/prototypes | Libraries/apps |