ExamplesBy LevelBy TopicLearning Paths
439 Fundamental

439: Assert Variant Macros

Functional Programming

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

  • • Understand how matches!($val, $pattern) provides a boolean pattern check
  • • Learn how assert_matches! combines matches! with assertion and error message
  • • See how unwrap_variant! uses match with a wildcard arm to extract or panic
  • • Understand the stringify!($pattern) technique for showing the pattern in error messages
  • • Learn that assert_matches! was stabilized in std in Rust 1.73
  • Code 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

  • Std stabilization: Rust's assert_matches! is now in std (1.73+); OCaml's test assertions are always library-based.
  • Pattern syntax: Rust's assert_matches!($v, Ok(x) if x > 0) supports guard conditions; OCaml's inline pattern matching does too.
  • Error messages: Rust's custom assert_matches! can include the actual value; OCaml's pattern match failures show the match expression location.
  • Extraction: Rust's 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);
        }
    }
    ✓ Tests Rust test suite
    #[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

  • Rust macros operate at compile time
  • OCaml uses ppx for similar metaprogramming
  • Both languages support powerful code generation
  • Rust's macro_rules! is built into the language
  • OCaml's approach requires external tooling
  • Exercises

  • Nested variant assertion: Extend 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.
  • Result helpers: Create 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.
  • Collection variant test: Implement assert_all_match!(items, $pattern) that asserts every element in a Vec matches the pattern, reporting the index of the first non-matching element.
  • Open Source Repos