431: Counting Patterns in Macros
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
[()].len()) is O(1) compile time vs. O(n) for recursive counting@single $_:tt => () converts each token to a unit value for array length countingrecursion_limit)[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
List.length is a runtime function.List.length fields.const values from macro counting; OCaml can encode lengths in types using GADTs for type-level length checking.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);
}
}#[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
Exercises
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.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.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.