โข 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
// 1003: Custom Error Types
// Custom error type with Display + Error impl
use std::fmt;
// Approach 1: Simple error enum with Display
#[derive(Debug, PartialEq)]
enum ValidationError {
NegativeAge(i32),
UnreasonableAge(i32),
EmptyName,
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValidationError::NegativeAge(n) => write!(f, "negative age: {}", n),
ValidationError::UnreasonableAge(n) => write!(f, "unreasonable age: {}", n),
ValidationError::EmptyName => write!(f, "name cannot be empty"),
}
}
}
impl std::error::Error for ValidationError {}
fn validate_age(age: i32) -> Result<i32, ValidationError> {
if age < 0 {
Err(ValidationError::NegativeAge(age))
} else if age > 150 {
Err(ValidationError::UnreasonableAge(age))
} else {
Ok(age)
}
}
fn validate_name(name: &str) -> Result<&str, ValidationError> {
if name.is_empty() {
Err(ValidationError::EmptyName)
} else {
Ok(name)
}
}
// Approach 2: Error with structured context
#[derive(Debug)]
struct DetailedError {
field: String,
message: String,
}
impl fmt::Display for DetailedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "field '{}': {}", self.field, self.message)
}
}
impl std::error::Error for DetailedError {}
fn validate_field(field: &str, value: &str) -> Result<(), DetailedError> {
if value.is_empty() {
Err(DetailedError {
field: field.to_string(),
message: "cannot be empty".to_string(),
})
} else {
Ok(())
}
}
fn main() {
// Demo usage
match validate_age(-5) {
Ok(age) => println!("Valid age: {}", age),
Err(e) => println!("Error: {}", e),
}
match validate_field("email", "") {
Ok(()) => println!("Valid field"),
Err(e) => println!("Error: {}", e),
}
println!("Run `cargo test` to verify all examples.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_age() {
assert_eq!(validate_age(25), Ok(25));
}
#[test]
fn test_negative_age() {
assert_eq!(validate_age(-5), Err(ValidationError::NegativeAge(-5)));
}
#[test]
fn test_unreasonable_age() {
assert_eq!(validate_age(200), Err(ValidationError::UnreasonableAge(200)));
}
#[test]
fn test_display_impl() {
let err = ValidationError::NegativeAge(-1);
assert_eq!(err.to_string(), "negative age: -1");
let err = ValidationError::EmptyName;
assert_eq!(err.to_string(), "name cannot be empty");
}
#[test]
fn test_error_trait() {
let err: Box<dyn std::error::Error> = Box::new(ValidationError::EmptyName);
assert_eq!(err.to_string(), "name cannot be empty");
}
#[test]
fn test_validate_name() {
assert_eq!(validate_name("Alice"), Ok("Alice"));
assert_eq!(validate_name(""), Err(ValidationError::EmptyName));
}
#[test]
fn test_detailed_error() {
let result = validate_field("email", "");
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "field 'email': cannot be empty");
}
}
(* 1003: Custom Error Types *)
(* OCaml exception vs Rust enum-based errors *)
(* Approach 1: Built-in exceptions *)
exception Invalid_age of string
exception Parse_error of string
let validate_age_exn age =
if age < 0 then raise (Invalid_age "age cannot be negative")
else if age > 150 then raise (Invalid_age "age unreasonably large")
else age
let test_approach1 () =
assert (validate_age_exn 25 = 25);
(try
let _ = validate_age_exn (-1) in
assert false
with Invalid_age msg -> assert (msg = "age cannot be negative"));
(try
let _ = validate_age_exn 200 in
assert false
with Invalid_age msg -> assert (msg = "age unreasonably large"));
Printf.printf " Approach 1 (exceptions): passed\n"
(* Approach 2: Result type with custom error variant *)
type validation_error =
| NegativeAge of int
| UnreasonableAge of int
| EmptyName
let string_of_validation_error = function
| NegativeAge n -> Printf.sprintf "negative age: %d" n
| UnreasonableAge n -> Printf.sprintf "unreasonable age: %d" n
| EmptyName -> "name cannot be empty"
let validate_age age =
if age < 0 then Error (NegativeAge age)
else if age > 150 then Error (UnreasonableAge age)
else Ok age
let validate_name name =
if String.length name = 0 then Error EmptyName
else Ok name
let test_approach2 () =
assert (validate_age 25 = Ok 25);
assert (validate_age (-5) = Error (NegativeAge (-5)));
assert (validate_age 200 = Error (UnreasonableAge 200));
assert (validate_name "Alice" = Ok "Alice");
assert (validate_name "" = Error EmptyName);
(match validate_age (-1) with
| Error e -> assert (string_of_validation_error e = "negative age: -1")
| Ok _ -> assert false);
Printf.printf " Approach 2 (result type): passed\n"
(* Approach 3: Polymorphic variants for lightweight errors *)
let validate_age_poly age =
if age < 0 then Error (`NegativeAge age)
else if age > 150 then Error (`UnreasonableAge age)
else Ok age
let test_approach3 () =
assert (validate_age_poly 30 = Ok 30);
(match validate_age_poly (-1) with
| Error (`NegativeAge n) -> assert (n = -1)
| _ -> assert false);
Printf.printf " Approach 3 (polymorphic variants): passed\n"
let () =
Printf.printf "Testing custom error types:\n";
test_approach1 ();
test_approach2 ();
test_approach3 ();
Printf.printf "โ All tests passed\n"
| Aspect | OCaml | Rust |
|---|---|---|
| Error declaration | `exception Foo of string` | `enum MyError { Foo(String) }` |
| Type visibility | Not in signature | In `Result<T, E>` return type |
| Pattern matching | `try ... with` | `match result { Ok/Err }` |
| Exhaustiveness | No (catch-all needed) | Yes (compiler enforced) |
| Display | Manual `string_of_*` | `impl Display` trait |
| Composability | Limited | `Error` trait + `From` + `?` |