๐Ÿฆ€ Functional Rust
๐ŸŽฌ Error Handling in Rust Option, Result, the ? operator, and combinators.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Option represents a value that may or may not exist โ€” Some(value) or None

โ€ข Result represents success (Ok) or failure (Err) โ€” no exceptions needed

โ€ข 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

319: Error Handling in Tests

Difficulty: 1 Level: Beginner Write clean, readable tests for fallible code โ€” including the failure cases.

The Problem This Solves

You have a function that returns `Result<T, E>`. Testing both the success and failure paths requires different approaches: success tests want to extract the value and check it, failure tests want to assert on the error variant, and some tests need to verify that code panics. Writing all of these with manual `match` expressions is verbose and obscures what's actually being tested. Rust's test framework handles this well โ€” but you need to know the idioms. Result-returning test functions (`fn test() -> Result<(), E>`) let you use `?` freely inside tests. `assert_eq!` on `Result` values checks equality. `#[should_panic]` marks tests that are expected to panic. And `unwrap_err()` extracts the error for inspection without a verbose match. Testing error paths thoroughly is what separates production-quality code from prototype code. Error handling that's never been tested is error handling that doesn't work when you need it.

The Intuition

Test functions can return `Result<(), E>` โ€” use `?` freely for the happy path, and `unwrap_err()` / `assert_eq!` for the error path.

How It Works in Rust

#[cfg(test)]
mod tests {
 use super::*;

 // Pattern 1: Result-returning test โ€” use ? freely
 #[test]
 fn div_ok() -> Result<(), MathError> {
     assert_eq!(safe_div(10, 2)?, 5);  // ? fails the test if Err
     Ok(())  // must return Ok(()) at the end
 }

 // Pattern 2: Assert on specific error value
 #[test]
 fn div_zero() {
     assert_eq!(safe_div(5, 0), Err(MathError::DivisionByZero));
 }

 // Pattern 3: Check error without equality โ€” matches! macro
 #[test]
 fn div_zero_variant() {
     assert!(matches!(safe_div(5, 0), Err(MathError::DivisionByZero)));
 }

 // Pattern 4: Inspect the error value
 #[test]
 fn sqrt_neg_message() {
     let err = safe_sqrt(-9).unwrap_err();  // unwrap_err() panics if Ok
     assert_eq!(err, MathError::NegativeInput(-9));
 }

 // Pattern 5: Test that a function panics
 #[test]
 #[should_panic]           // test passes if the body panics
 fn panics_on_unwrap() {
     safe_div(1, 0).unwrap();  // this panics
 }

 // Pattern 6: #[should_panic(expected = "...")] โ€” verify the panic message
 #[test]
 #[should_panic(expected = "division by zero")]
 fn panics_with_message() {
     panic!("division by zero");
 }
}
The `?` operator in tests converts an error into a test failure with the error's `Debug` output. It's cleaner than `unwrap()` because the test failure message tells you which error occurred, not just "called unwrap on an Err value."

What This Unlocks

Key Differences

ConceptOCamlRust
Assert equality`assert_equal` (OUnit)`assert_eq!(actual, expected)`
Expected error`assert_raises``assert_eq!(f(), Err(e))` or `#[should_panic]`
Result in testsManual `match``fn test() -> Result<(), E>` โ€” use `?` directly
Inspect errorManual `match``result.unwrap_err()` โ€” panics if `Ok`
#[derive(Debug, PartialEq)]
enum MathError { DivisionByZero, NegativeInput(i64) }

impl std::fmt::Display for MathError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::DivisionByZero => write!(f, "division by zero"),
            Self::NegativeInput(n) => write!(f, "negative input: {n}"),
        }
    }
}

fn safe_div(a: i64, b: i64) -> Result<i64, MathError> {
    if b == 0 { Err(MathError::DivisionByZero) } else { Ok(a / b) }
}

fn safe_sqrt(x: i64) -> Result<u64, MathError> {
    if x < 0 { Err(MathError::NegativeInput(x)) } else { Ok((x as f64).sqrt() as u64) }
}

fn main() {
    println!("{:?}", safe_div(10, 2));
    println!("{:?}", safe_div(10, 0));
    println!("{:?}", safe_sqrt(16));
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn div_ok() -> Result<(), MathError> { assert_eq!(safe_div(10,2)?, 5); Ok(()) }
    #[test] fn div_zero() { assert_eq!(safe_div(5,0), Err(MathError::DivisionByZero)); }
    #[test] fn sqrt_ok() -> Result<(), MathError> { assert_eq!(safe_sqrt(16)?, 4); Ok(()) }
    #[test] fn sqrt_neg() { assert_eq!(safe_sqrt(-9).unwrap_err(), MathError::NegativeInput(-9)); }
    #[test] #[should_panic] fn panics_on_unwrap() { safe_div(1,0).unwrap(); }
}
(* OCaml: testing patterns *)

let safe_div a b =
  if b = 0 then Error "division by zero"
  else Ok (a / b)

let safe_sqrt x =
  if x < 0 then Error (Printf.sprintf "negative: %d" x)
  else Ok (int_of_float (sqrt (float_of_int x)))

let test_div () =
  assert (safe_div 10 2 = Ok 5);
  assert (safe_div 7 0 = Error "division by zero");
  print_endline "div tests passed"

let test_sqrt () =
  assert (safe_sqrt 16 = Ok 4);
  assert (safe_sqrt (-1) = Error "negative: -1");
  print_endline "sqrt tests passed"

let () = test_div (); test_sqrt ()