ExamplesBy LevelBy TopicLearning Paths
431 Fundamental

431: Counting Patterns in Macros

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "431: Counting Patterns in Macros" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Many macro-generated code patterns need to know the number of elements at compile time: pre-allocating arrays of the right size, generating tuple types, creating assertions about argument counts. Key difference from OCaml: 1. **Compile vs. runtime**: Rust's count macros produce compile

Tutorial

The Problem

Many macro-generated code patterns need to know the number of elements at compile time: pre-allocating arrays of the right size, generating tuple types, creating assertions about argument counts. Counting macro arguments is surprisingly non-trivial — you can't use a simple $n count since macros don't have builtin counters. Three techniques exist: recursive counting (O(n) expansions), array length trick (O(1) using [()].len()), and the substitution trick. Each has different compile-time performance characteristics.

Counting patterns appear in static_assertions (checking type sizes), array initialization macros, compile-time tuple generation, and any macro needing to allocate the right amount of space.

🎯 Learning Outcomes

  • • Understand three approaches to counting macro arguments: recursion, array trick, expression counting
  • • Learn why the array trick ([()].len()) is O(1) compile time vs. O(n) for recursive counting
  • • See how @single $_:tt => () converts each token to a unit value for array length counting
  • • Understand when compile-time counts matter for performance (deeply recursive macros can hit recursion_limit)
  • • Learn how to use counts in array initialization: [default_val; count_array!($($x),*)]
  • Code Example

    #![allow(clippy::all)]
    //! Counting Patterns in Macros
    //!
    //! Techniques for counting macro arguments.
    
    /// Count using recursion.
    #[macro_export]
    macro_rules! count_recursive {
        () => { 0usize };
        ($head:tt $($tail:tt)*) => { 1 + count_recursive!($($tail)*) };
    }
    
    /// Count using array trick.
    #[macro_export]
    macro_rules! count_array {
        (@single $_:tt) => { () };
        ($($x:tt)*) => {
            <[()]>::len(&[$(count_array!(@single $x)),*])
        };
    }
    
    /// Count expressions.
    #[macro_export]
    macro_rules! count_exprs {
        () => { 0 };
        ($e:expr) => { 1 };
        ($e:expr, $($rest:expr),+) => { 1 + count_exprs!($($rest),+) };
    }
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_count_recursive_empty() {
            assert_eq!(count_recursive!(), 0);
        }
    
        #[test]
        fn test_count_recursive() {
            assert_eq!(count_recursive!(a b c d e), 5);
        }
    
        #[test]
        fn test_count_array_empty() {
            assert_eq!(count_array!(), 0);
        }
    
        #[test]
        fn test_count_array() {
            assert_eq!(count_array!(1 2 3), 3);
        }
    
        #[test]
        fn test_count_exprs() {
            assert_eq!(count_exprs!(1, 2, 3, 4), 4);
        }
    
        #[test]
        fn test_count_exprs_single() {
            assert_eq!(count_exprs!(42), 1);
        }
    }

    Key Differences

  • Compile vs. runtime: Rust's count macros produce compile-time constants; OCaml's List.length is a runtime function.
  • Technique: Rust needs tricks (array trick, recursion) because macros don't have built-in counters; OCaml PPX can directly use List.length fields.
  • Type-level: Rust uses const values from macro counting; OCaml can encode lengths in types using GADTs for type-level length checking.
  • Performance: The array trick is O(1) expansions; the recursive approach is O(n) expansions where n is the element count.
  • OCaml Approach

    OCaml counts list elements at runtime with List.length. At compile time, OCaml uses type-level numbers (Peano arithmetic with GADTs or type-level naturals from the zarith library). PPX can count AST nodes during compilation. There's no direct equivalent of Rust's token-counting trick since OCaml PPX operates on the AST, where List.length on fields is straightforward.

    Full Source

    #![allow(clippy::all)]
    //! Counting Patterns in Macros
    //!
    //! Techniques for counting macro arguments.
    
    /// Count using recursion.
    #[macro_export]
    macro_rules! count_recursive {
        () => { 0usize };
        ($head:tt $($tail:tt)*) => { 1 + count_recursive!($($tail)*) };
    }
    
    /// Count using array trick.
    #[macro_export]
    macro_rules! count_array {
        (@single $_:tt) => { () };
        ($($x:tt)*) => {
            <[()]>::len(&[$(count_array!(@single $x)),*])
        };
    }
    
    /// Count expressions.
    #[macro_export]
    macro_rules! count_exprs {
        () => { 0 };
        ($e:expr) => { 1 };
        ($e:expr, $($rest:expr),+) => { 1 + count_exprs!($($rest),+) };
    }
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_count_recursive_empty() {
            assert_eq!(count_recursive!(), 0);
        }
    
        #[test]
        fn test_count_recursive() {
            assert_eq!(count_recursive!(a b c d e), 5);
        }
    
        #[test]
        fn test_count_array_empty() {
            assert_eq!(count_array!(), 0);
        }
    
        #[test]
        fn test_count_array() {
            assert_eq!(count_array!(1 2 3), 3);
        }
    
        #[test]
        fn test_count_exprs() {
            assert_eq!(count_exprs!(1, 2, 3, 4), 4);
        }
    
        #[test]
        fn test_count_exprs_single() {
            assert_eq!(count_exprs!(42), 1);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_count_recursive_empty() {
            assert_eq!(count_recursive!(), 0);
        }
    
        #[test]
        fn test_count_recursive() {
            assert_eq!(count_recursive!(a b c d e), 5);
        }
    
        #[test]
        fn test_count_array_empty() {
            assert_eq!(count_array!(), 0);
        }
    
        #[test]
        fn test_count_array() {
            assert_eq!(count_array!(1 2 3), 3);
        }
    
        #[test]
        fn test_count_exprs() {
            assert_eq!(count_exprs!(1, 2, 3, 4), 4);
        }
    
        #[test]
        fn test_count_exprs_single() {
            assert_eq!(count_exprs!(42), 1);
        }
    }

    Deep Comparison

    OCaml vs Rust: macro count pattern

    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

  • Fixed-size tuple macro: Implement tuple!(1, 2, 3) that generates a tuple literal AND uses count_array! to initialize an array [0usize; count_array!(1, 2, 3)] of the same length.
  • Sized collection: Create static_vec!(T, 1, 2, 3) that generates let arr: [T; COUNT] = [1, 2, 3] where COUNT is the compile-time count. Verify that the array size matches the element count.
  • Argument count assertion: Write assert_arity!(fn_name, 3) that at compile time asserts a tuple has exactly 3 elements, generating a static_assertions::const_assert_eq!(COUNT, 3) check.
  • Open Source Repos