ExamplesBy LevelBy TopicLearning Paths
412 Fundamental

412: Macro Repetition Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "412: Macro Repetition Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Variadic functions (accepting any number of arguments) are fundamental to ergonomic APIs: `println!`, `vec!`, `format!`. Key difference from OCaml: 1. **Native variadic**: OCaml functions can accept lists/options making many macro use cases unnecessary; Rust requires macros for variadic behavior.

Tutorial

The Problem

Variadic functions (accepting any number of arguments) are fundamental to ergonomic APIs: println!, vec!, format!. In Rust, regular functions cannot be variadic — they have fixed arities. macro_rules! repetition syntax $($pattern),* matches zero or more occurrences and expands them, enabling macros that accept any number of arguments. This is how vec![1, 2, 3] works for any length, and how println!("{} {}", a, b) accepts different format argument counts.

Repetition patterns appear in every variadic macro: assert_matches!, matches!, dbg!, join! in async code, and custom test helpers.

🎯 Learning Outcomes

  • • Understand $(...),* (zero or more) and $(...),+ (one or more) repetition syntax
  • • Learn how to expand repetitions into expressions, statements, and item definitions
  • • See how trailing comma handling with $(,)? improves ergonomics
  • • Understand nested repetitions for multi-level variadic patterns
  • • Learn how repetition interacts with fragment types
  • Code Example

    macro_rules! sum {
        () => { 0 };
        ($first:expr $(, $rest:expr)*) => {
            $first $(+ $rest)*
        };
    }
    
    macro_rules! product {
        () => { 1 };
        ($first:expr $(, $rest:expr)*) => {
            $first $(* $rest)*
        };
    }
    
    fn main() {
        println!("sum: {}", sum!(1, 2, 3, 4, 5));
        println!("product: {}", product!(2, 3, 4));
    }

    Key Differences

  • Native variadic: OCaml functions can accept lists/options making many macro use cases unnecessary; Rust requires macros for variadic behavior.
  • Compile-time expansion: Rust's repetition macros expand at compile time with no runtime list; OCaml's list-based variadic functions use runtime list traversal.
  • Syntax flexibility: Rust macro repetition can match any token pattern; OCaml's variadic approaches are limited to function call syntax.
  • Type heterogeneity: Rust macros can accept values of different types in one repetition (via $tt); OCaml lists are homogeneous.
  • OCaml Approach

    OCaml functions are natively variadic through list arguments or optional parameters. List.fold_left (+) 0 values sums any list without macros. For syntax-level repetition (like match arm generation), OCaml uses PPX. The [%test_eq: int] x y syntax from Jane Street's ppx_jane generates comparison code through AST transformation — similar to Rust macro repetition but through a different mechanism.

    Full Source

    #![allow(clippy::all)]
    //! Macro Repetition Patterns
    //!
    //! Using $(...),* and $(...),+ for variadic macros.
    
    /// Sum any number of values.
    #[macro_export]
    macro_rules! sum {
        () => { 0 };
        ($first:expr $(, $rest:expr)*) => {
            $first $(+ $rest)*
        };
    }
    
    /// Product of any number of values.
    #[macro_export]
    macro_rules! product {
        () => { 1 };
        ($first:expr $(, $rest:expr)*) => {
            $first $(* $rest)*
        };
    }
    
    /// All values greater than threshold.
    #[macro_export]
    macro_rules! all_gt {
        ($threshold:expr; $($val:expr),+ $(,)?) => {
            true $(&& ($val > $threshold))+
        };
    }
    
    /// Any value equals target.
    #[macro_export]
    macro_rules! any_eq {
        ($target:expr; $($val:expr),+ $(,)?) => {
            false $(|| ($val == $target))+
        };
    }
    
    /// Create a HashMap from key-value pairs.
    #[macro_export]
    macro_rules! hashmap {
        () => {
            ::std::collections::HashMap::new()
        };
        ($($key:expr => $value:expr),+ $(,)?) => {
            {
                let mut map = ::std::collections::HashMap::new();
                $(map.insert($key, $value);)+
                map
            }
        };
    }
    
    /// Create a struct with fields from macro.
    #[macro_export]
    macro_rules! define_struct {
        ($name:ident { $($field:ident : $ty:ty),* $(,)? }) => {
            #[derive(Debug, Default, Clone, PartialEq)]
            pub struct $name {
                $(pub $field: $ty,)*
            }
        };
    }
    
    define_struct!(Config {
        host: String,
        port: u16,
        timeout: u32,
    });
    
    /// Print a table of key-value pairs.
    #[macro_export]
    macro_rules! print_table {
        ($($key:expr => $val:expr),* $(,)?) => {
            $(println!("{:>20}: {}", $key, $val);)*
        };
    }
    
    /// Count the number of arguments.
    #[macro_export]
    macro_rules! count_args {
        () => { 0usize };
        ($first:expr $(, $rest:expr)*) => {
            1usize $(+ { let _ = $rest; 1usize })*
        };
    }
    
    /// Create a Vec with a transformation applied.
    #[macro_export]
    macro_rules! vec_transform {
        ($f:expr; $($x:expr),* $(,)?) => {
            vec![$($f($x)),*]
        };
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_sum_empty() {
            assert_eq!(sum!(), 0);
        }
    
        #[test]
        fn test_sum_single() {
            assert_eq!(sum!(42), 42);
        }
    
        #[test]
        fn test_sum_multiple() {
            assert_eq!(sum!(1, 2, 3, 4, 5), 15);
        }
    
        #[test]
        fn test_product_empty() {
            assert_eq!(product!(), 1);
        }
    
        #[test]
        fn test_product_multiple() {
            assert_eq!(product!(2, 3, 4), 24);
        }
    
        #[test]
        fn test_all_gt_true() {
            assert!(all_gt!(0; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_all_gt_false() {
            assert!(!all_gt!(2; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_any_eq_true() {
            assert!(any_eq!(3; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_any_eq_false() {
            assert!(!any_eq!(10; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_hashmap_empty() {
            let m: std::collections::HashMap<&str, i32> = hashmap!();
            assert!(m.is_empty());
        }
    
        #[test]
        fn test_hashmap_entries() {
            let m = hashmap! {
                "a" => 1,
                "b" => 2,
            };
            assert_eq!(m["a"], 1);
            assert_eq!(m["b"], 2);
        }
    
        #[test]
        fn test_define_struct() {
            let cfg = Config {
                host: "localhost".to_string(),
                port: 8080,
                timeout: 30,
            };
            assert_eq!(cfg.port, 8080);
        }
    
        #[test]
        fn test_count_args() {
            assert_eq!(count_args!(), 0);
            assert_eq!(count_args!(1), 1);
            assert_eq!(count_args!(1, 2, 3), 3);
            assert_eq!(count_args!(1, 2, 3, 4, 5), 5);
        }
    
        #[test]
        fn test_vec_transform() {
            let v = vec_transform!(|x| x * 2; 1, 2, 3);
            assert_eq!(v, vec![2, 4, 6]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_sum_empty() {
            assert_eq!(sum!(), 0);
        }
    
        #[test]
        fn test_sum_single() {
            assert_eq!(sum!(42), 42);
        }
    
        #[test]
        fn test_sum_multiple() {
            assert_eq!(sum!(1, 2, 3, 4, 5), 15);
        }
    
        #[test]
        fn test_product_empty() {
            assert_eq!(product!(), 1);
        }
    
        #[test]
        fn test_product_multiple() {
            assert_eq!(product!(2, 3, 4), 24);
        }
    
        #[test]
        fn test_all_gt_true() {
            assert!(all_gt!(0; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_all_gt_false() {
            assert!(!all_gt!(2; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_any_eq_true() {
            assert!(any_eq!(3; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_any_eq_false() {
            assert!(!any_eq!(10; 1, 2, 3, 4, 5));
        }
    
        #[test]
        fn test_hashmap_empty() {
            let m: std::collections::HashMap<&str, i32> = hashmap!();
            assert!(m.is_empty());
        }
    
        #[test]
        fn test_hashmap_entries() {
            let m = hashmap! {
                "a" => 1,
                "b" => 2,
            };
            assert_eq!(m["a"], 1);
            assert_eq!(m["b"], 2);
        }
    
        #[test]
        fn test_define_struct() {
            let cfg = Config {
                host: "localhost".to_string(),
                port: 8080,
                timeout: 30,
            };
            assert_eq!(cfg.port, 8080);
        }
    
        #[test]
        fn test_count_args() {
            assert_eq!(count_args!(), 0);
            assert_eq!(count_args!(1), 1);
            assert_eq!(count_args!(1, 2, 3), 3);
            assert_eq!(count_args!(1, 2, 3, 4, 5), 5);
        }
    
        #[test]
        fn test_vec_transform() {
            let v = vec_transform!(|x| x * 2; 1, 2, 3);
            assert_eq!(v, vec![2, 4, 6]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Macro Repetition

    Side-by-Side Code

    OCaml — Variadic via lists

    let sum_list xs = List.fold_left (+) 0 xs
    let product_list xs = List.fold_left ( * ) 1 xs
    
    let () =
      Printf.printf "sum: %d\n" (sum_list [1;2;3;4;5]);
      Printf.printf "product: %d\n" (product_list [2;3;4])
    

    Rust — Macro repetition

    macro_rules! sum {
        () => { 0 };
        ($first:expr $(, $rest:expr)*) => {
            $first $(+ $rest)*
        };
    }
    
    macro_rules! product {
        () => { 1 };
        ($first:expr $(, $rest:expr)*) => {
            $first $(* $rest)*
        };
    }
    
    fn main() {
        println!("sum: {}", sum!(1, 2, 3, 4, 5));
        println!("product: {}", product!(2, 3, 4));
    }
    

    Comparison Table

    AspectOCamlRust
    Variadic argsListsMacro repetition
    Syntax[1;2;3]sum!(1, 2, 3)
    Type checkingAt call siteDuring expansion
    PerformanceList allocationZero-cost (inlined)

    Repetition Patterns

    // Zero or more (*)
    $(pattern),* 
    
    // One or more (+)
    $(pattern),+
    
    // Zero or one (?)
    $(pattern)?
    

    Examples

    // Zero or more items
    macro_rules! vec_of {
        ($($x:expr),* $(,)?) => { vec![$($x),*] };
    }
    
    // At least one item
    macro_rules! min {
        ($x:expr $(, $y:expr)+) => { ... };
    }
    
    // Optional trailing comma
    macro_rules! map {
        ($($k:expr => $v:expr),+ $(,)?) => { ... };
    }
    

    5 Takeaways

  • OCaml uses lists for variadic; Rust uses macro repetition.
  • [1;2;3] vs sum!(1, 2, 3).

  • Rust macros expand at compile time.
  • No runtime overhead from list construction.

  • **$(...)* = zero or more; $(...)+ = one or more.**
  • Choose based on whether empty input is valid.

  • **Trailing comma: $(,)? makes it optional.**
  • Common pattern for comma-separated macros.

  • Repetition in expansion mirrors capture.
  • $(+ $rest)* repeats the + for each captured $rest.

    Exercises

  • Max of n: Implement max_of!(a, b, c, ...) analogously to min_of! — returning the maximum of any number of comparable expressions.
  • Cartesian product: Implement cartesian!(($a1, $a2, ...), ($b1, $b2, ...)) that generates a Vec of all pairs. Use nested repetition.
  • Batch assert: Implement assert_all_eq!(($a, $b), ($c, $d), ...) that asserts each pair is equal, with the pair index in the panic message so failures identify which pair failed.
  • Open Source Repos