439: Assert Variant Macros
Tutorial Video
Text description (accessibility)
This video demonstrates the "439: Assert Variant Macros" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Testing enum variants is verbose: `assert!(matches!(result, Ok(_)))` requires knowing the `matches!` macro, wrapping in `assert!`, and loses the ability to extract the inner value. Key difference from OCaml: 1. **Std stabilization**: Rust's `assert_matches!` is now in `std` (1.73+); OCaml's test assertions are always library
Tutorial
The Problem
Testing enum variants is verbose: assert!(matches!(result, Ok(_))) requires knowing the matches! macro, wrapping in assert!, and loses the ability to extract the inner value. unwrap_variant!(result, Ok(v) => v) provides a cleaner pattern: assert that the value matches a variant and extract the inner data in one operation. For test suites heavily using Result and enum-heavy domain models, these macros significantly reduce test boilerplate while providing better error messages.
Variant assertion macros appear in assert_matches! (stabilized in Rust 1.73), unwrap_variant, testing framework helper crates, and any codebase testing enum-heavy APIs.
🎯 Learning Outcomes
matches!($val, $pattern) provides a boolean pattern checkassert_matches! combines matches! with assertion and error messageunwrap_variant! uses match with a wildcard arm to extract or panicstringify!($pattern) technique for showing the pattern in error messagesassert_matches! was stabilized in std in Rust 1.73Code Example
#![allow(clippy::all)]
//! Assert Variant Macros
//!
//! Testing enum variants.
/// Assert that value matches pattern.
#[macro_export]
macro_rules! assert_matches {
($value:expr, $pattern:pat) => {
assert!(
matches!($value, $pattern),
"assertion failed: `{:?}` does not match `{}`",
$value,
stringify!($pattern)
);
};
}
/// Extract variant or panic.
#[macro_export]
macro_rules! unwrap_variant {
($value:expr, $pattern:pat => $extracted:expr) => {
match $value {
$pattern => $extracted,
_ => panic!("Expected {}", stringify!($pattern)),
}
};
}
#[derive(Debug)]
pub enum Result<T, E> {
Ok(T),
Err(E),
}
#[derive(Debug)]
pub enum Message {
Text(String),
Number(i32),
Empty,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assert_matches_ok() {
let r: std::result::Result<i32, &str> = Ok(42);
assert_matches!(r, Ok(_));
}
#[test]
fn test_assert_matches_some() {
let o = Some(5);
assert_matches!(o, Some(_));
}
#[test]
fn test_unwrap_variant() {
let m = Message::Number(42);
let n = unwrap_variant!(m, Message::Number(x) => x);
assert_eq!(n, 42);
}
#[test]
fn test_assert_matches_text() {
let m = Message::Text("hello".into());
assert_matches!(m, Message::Text(_));
}
#[test]
fn test_assert_matches_empty() {
let m = Message::Empty;
assert_matches!(m, Message::Empty);
}
}Key Differences
assert_matches! is now in std (1.73+); OCaml's test assertions are always library-based.assert_matches!($v, Ok(x) if x > 0) supports guard conditions; OCaml's inline pattern matching does too.assert_matches! can include the actual value; OCaml's pattern match failures show the match expression location.unwrap_variant! extracts the inner value; OCaml's let Pattern x = val achieves the same with more concise syntax.OCaml Approach
OCaml's alcotest provides check (module Message) "msg" expected actual with custom testable modules. assert_failure "message" in OUnit2 handles failure. For pattern-based tests, OCaml's match ... with _ -> assert false is the equivalent of unwrap_variant!. OCaml's pattern matching is a language feature, so tests are often just let Message.Text s = msg in assert_string s.
Full Source
#![allow(clippy::all)]
//! Assert Variant Macros
//!
//! Testing enum variants.
/// Assert that value matches pattern.
#[macro_export]
macro_rules! assert_matches {
($value:expr, $pattern:pat) => {
assert!(
matches!($value, $pattern),
"assertion failed: `{:?}` does not match `{}`",
$value,
stringify!($pattern)
);
};
}
/// Extract variant or panic.
#[macro_export]
macro_rules! unwrap_variant {
($value:expr, $pattern:pat => $extracted:expr) => {
match $value {
$pattern => $extracted,
_ => panic!("Expected {}", stringify!($pattern)),
}
};
}
#[derive(Debug)]
pub enum Result<T, E> {
Ok(T),
Err(E),
}
#[derive(Debug)]
pub enum Message {
Text(String),
Number(i32),
Empty,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assert_matches_ok() {
let r: std::result::Result<i32, &str> = Ok(42);
assert_matches!(r, Ok(_));
}
#[test]
fn test_assert_matches_some() {
let o = Some(5);
assert_matches!(o, Some(_));
}
#[test]
fn test_unwrap_variant() {
let m = Message::Number(42);
let n = unwrap_variant!(m, Message::Number(x) => x);
assert_eq!(n, 42);
}
#[test]
fn test_assert_matches_text() {
let m = Message::Text("hello".into());
assert_matches!(m, Message::Text(_));
}
#[test]
fn test_assert_matches_empty() {
let m = Message::Empty;
assert_matches!(m, Message::Empty);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assert_matches_ok() {
let r: std::result::Result<i32, &str> = Ok(42);
assert_matches!(r, Ok(_));
}
#[test]
fn test_assert_matches_some() {
let o = Some(5);
assert_matches!(o, Some(_));
}
#[test]
fn test_unwrap_variant() {
let m = Message::Number(42);
let n = unwrap_variant!(m, Message::Number(x) => x);
assert_eq!(n, 42);
}
#[test]
fn test_assert_matches_text() {
let m = Message::Text("hello".into());
assert_matches!(m, Message::Text(_));
}
#[test]
fn test_assert_matches_empty() {
let m = Message::Empty;
assert_matches!(m, Message::Empty);
}
}
Deep Comparison
OCaml vs Rust: macro assert variants
See example.rs and example.ml for side-by-side implementations.
Key Points
Exercises
assert_matches! to support nested patterns: assert_matches!(response, Response::Ok(Body::Json(json)) if json.contains("id")). Verify it produces clear failure messages showing the actual value.assert_ok!(result) and assert_err!(result) macros that assert the appropriate variant and return the inner value. Also create assert_ok_eq!(result, expected) that combines the assertion with value equality checking.assert_all_match!(items, $pattern) that asserts every element in a Vec matches the pattern, reporting the index of the first non-matching element.