โข 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
#[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."
| Concept | OCaml | Rust |
|---|---|---|
| Assert equality | `assert_equal` (OUnit) | `assert_eq!(actual, expected)` |
| Expected error | `assert_raises` | `assert_eq!(f(), Err(e))` or `#[should_panic]` |
| Result in tests | Manual `match` | `fn test() -> Result<(), E>` โ use `?` directly |
| Inspect error | Manual `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 ()