๐Ÿฆ€ Functional Rust

439: assert_matches! and Variant Assertions

Difficulty: 2 Level: Intermediate Use `matches!` and `assert_matches!` to write pattern-match assertions โ€” checking enum variants and guard conditions in one readable expression without manually destructuring.

The Problem This Solves

Testing functions that return enums is awkward with `assert_eq!`. You either need to derive `PartialEq` on every enum (including ones where equality semantics are unclear), or you write verbose destructuring in every test:
match parse("42") {
 Parsed::Int(n) => assert_eq!(n, 42),
 other => panic!("Expected Int(42), got {:?}", other),
}
That's five lines for one assertion. Multiply by a dozen tests and your test suite is more boilerplate than logic. It also doesn't compose โ€” you can't use it inside `assert!()` or `all()`. `matches!($val, Pattern)` solves this: it's a boolean expression that evaluates to `true` if `$val` matches the pattern. You can add guards (`if n > 50`), use it in `assert!`, compose it with `.all()` over iterators, or use it in `if` conditions without creating a separate variable.

The Intuition

`matches!(expr, pattern)` is a syntactic shorthand for:
match expr { pattern => true, _ => false }
The compiler desugars it exactly that way โ€” no overhead, no allocation, just a match expression. The `if` guard syntax works too: `matches!(x, Some(n) if n > 0)` becomes `match x { Some(n) if n > 0 => true, _ => false }`. `assert_matches!` (stabilised in Rust 1.82) adds a panic message when the match fails, showing both the pattern and the actual value. For earlier Rust versions, `assert!(matches!(...))` gives the same effect with a slightly less informative message.

How It Works in Rust

#[derive(Debug, PartialEq)]
enum Parsed { Int(i64), Float(f64), Invalid(String) }

fn parse(s: &str) -> Parsed { /* ... */ }

// โ”€โ”€ matches! in assertions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
assert!(matches!(parse("42"), Parsed::Int(42)));
assert!(matches!(parse("3.14"), Parsed::Float(_)));    // _ matches any value
assert!(matches!(parse("abc"), Parsed::Invalid(_)));

// โ”€โ”€ With guard conditions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
assert!(matches!(parse("100"), Parsed::Int(n) if n > 50));
assert!(matches!(parse("3.14"), Parsed::Float(f) if f > 3.0));

// โ”€โ”€ In if conditions โ€” no binding variable needed โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if matches!(parse("3.14"), Parsed::Float(f) if f > 3.0) {
 println!("Got float > 3");
}

// โ”€โ”€ Composing with iterators โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
let results: Vec<Result<u32, String>> = vec![Ok(1), Ok(2), Ok(3)];
assert!(results.iter().all(|r| matches!(r, Ok(n) if *n > 0)));

// โ”€โ”€ assert_matches! (Rust 1.82+) โ€” shows pattern in failure message โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// #![feature(assert_matches)]  // or stable in 1.82+
// use std::assert_matches::assert_matches;
// assert_matches!(parse("42"), Parsed::Int(_));  // better failure message

// โ”€โ”€ Pattern matching without extracting the value โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
fn is_valid(s: &str) -> bool {
 !matches!(parse(s), Parsed::Invalid(_))
}

What This Unlocks

Key Differences

ConceptOCamlRust
Pattern-match assertion`OUnit: assert_equal` with custom equality; or `match ..._ -> assert_failure``matches!` macro โ€” inline boolean; `assert_matches!` in 1.82+
Guard in assertionInline `when` clause in pattern`matches!(x, Pat if guard)`
Composing with iterators`List.for_all (fun x -> match x with ...) lst``.all(xmatches!(x, Pattern))`
Partial match (ignore variant data)`match x with Foo _ -> true_ -> false``matches!(x, Foo(_))`
// 439. assert_matches! and variants

#[derive(Debug, PartialEq)]
enum Parsed { Int(i64), Float(f64), Invalid(String) }

fn parse(s: &str) -> Parsed {
    if let Ok(n) = s.parse::<i64>() { Parsed::Int(n) }
    else if let Ok(f) = s.parse::<f64>() { Parsed::Float(f) }
    else { Parsed::Invalid(s.to_string()) }
}

fn parse_positive(s: &str) -> Result<u32, String> {
    s.parse::<u32>().map_err(|e| e.to_string())
}

fn main() {
    let r = parse("42");
    // matches! โ€” inline pattern check, no allocation
    assert!(matches!(r, Parsed::Int(n) if n == 42), "expected Int(42)");

    // Use in conditional
    if matches!(parse("3.14"), Parsed::Float(f) if f > 3.0) {
        println!("Got float > 3");
    }

    // assert_matches! equivalent (use matches! + assert!)
    let results = vec![
        parse_positive("1"),
        parse_positive("2"),
        parse_positive("3"),
    ];
    assert!(results.iter().all(|r| matches!(r, Ok(n) if *n > 0)));
    println!("All assertions passed!");
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn test_matches_int()   { assert!(matches!(parse("7"),     Parsed::Int(7))); }
    #[test] fn test_matches_float() { assert!(matches!(parse("1.5"),   Parsed::Float(_))); }
    #[test] fn test_matches_guard() { assert!(matches!(parse("100"),   Parsed::Int(n) if n > 50)); }
    #[test] fn test_matches_err()   { assert!(matches!(parse_positive("-1"), Err(_))); }
    #[test] fn test_matches_ok()    { assert!(matches!(parse_positive("5"),  Ok(5))); }
}
(* 439. assert_matches! style โ€“ OCaml *)

let assert_matches ~msg pred v =
  if not (pred v) then failwith msg

let parse_positive s =
  match int_of_string_opt s with
  | Some n when n > 0 -> Ok n
  | Some _ -> Error "not positive"
  | None   -> Error "not a number"

let () =
  (* Pattern-match assertions *)
  (match parse_positive "42" with
   | Ok n when n > 0 -> Printf.printf "Ok positive: %d\n" n
   | _ -> failwith "expected Ok positive");

  assert_matches ~msg:"should be Error"
    (function Error _ -> true | _ -> false)
    (parse_positive "-5");

  (* matches! equivalent: check shape without unwrapping *)
  let is_ok = function Ok _ -> true | _ -> false in
  assert (is_ok (parse_positive "10"));
  Printf.printf "All assertions passed!\n"