ExamplesBy LevelBy TopicLearning Paths
413 Fundamental

413: Macro Fragment Types (Designators)

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "413: Macro Fragment Types (Designators)" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. `macro_rules!` patterns match different syntactic categories through designators: `$e:expr` matches any expression, `$i:ident` matches an identifier, `$t:ty` matches a type, `$p:pat` matches a pattern, `$b:block` matches a block. Key difference from OCaml: 1. **Token vs. AST**: Rust macros work on token streams; OCaml PPX works on the parsed AST. Rust is more flexible but requires manual parsing; OCaml has structure but requires AST knowledge.

Tutorial

The Problem

macro_rules! patterns match different syntactic categories through designators: $e:expr matches any expression, $i:ident matches an identifier, $t:ty matches a type, $p:pat matches a pattern, $b:block matches a block. Choosing the right designator is critical — expr captures the full expression including operators, while tt captures a single token tree. Misusing designators leads to confusing parse errors or overly restrictive macros that refuse valid inputs.

Understanding fragment types is essential for writing robust macros that accept natural Rust syntax rather than awkward restricted subsets.

🎯 Learning Outcomes

  • • Learn the complete set of macro fragment designators: expr, ident, ty, pat, literal, block, stmt, item, meta, tt, lifetime
  • • Understand what each designator accepts and its "NFA following token" restrictions
  • • See how ident enables generating method names, field names, and identifiers
  • • Learn how ty accepts full type expressions including generics
  • • Understand when to use tt (token tree) for maximum flexibility
  • Code Example

    macro_rules! dbg_expr {
        ($e:expr) => {
            println!("{} = {:?}", stringify!($e), $e);
        };
    }
    
    dbg_expr!(2 + 3 * 4);  // "2 + 3 * 4 = 14"

    Key Differences

  • Token vs. AST: Rust macros work on token streams; OCaml PPX works on the parsed AST. Rust is more flexible but requires manual parsing; OCaml has structure but requires AST knowledge.
  • Following token rules: Rust fragment designators have restrictions on what can follow them (e.g., after expr, only ,/;/) can appear); OCaml PPX has no such restrictions.
  • Identifier generation: Rust uses $ident:ident to capture and reuse identifiers; OCaml PPX uses Ast_builder.evar and Ast_builder.pvar to create identifier nodes.
  • Type capture: Rust's $t:ty captures full type syntax; OCaml PPX receives core_type which is already parsed into an AST node.
  • OCaml Approach

    OCaml PPX extensions work on the AST directly: [%ppx_gen field_name] receives the parsed Parsetree.expression or Parsetree.pattern value rather than tokens. PPX has direct access to parsed types (core_type), patterns (pattern), and expressions (expression) — more structured than Rust's token-level matching, but requiring knowledge of OCaml's AST types.

    Full Source

    #![allow(clippy::all)]
    //! Macro Fragment Types
    //!
    //! Different designators: expr, ident, ty, pat, literal, block, tt.
    
    /// Debug expression and return its value.
    #[macro_export]
    macro_rules! dbg_expr {
        ($e:expr) => {{
            let val = $e;
            println!("{} = {:?}", stringify!($e), val);
            val
        }};
    }
    
    /// Generate a getter method.
    #[macro_export]
    macro_rules! make_getter {
        ($field:ident : $ty:ty) => {
            pub fn $field(&self) -> &$ty {
                &self.$field
            }
        };
    }
    
    /// Generate a setter method.
    #[macro_export]
    macro_rules! make_setter {
        ($field:ident : $ty:ty) => {
            paste::item! {
                pub fn [<set_ $field>](&mut self, value: $ty) {
                    self.$field = value;
                }
            }
        };
    }
    
    /// Generate a default function for a type.
    #[macro_export]
    macro_rules! make_default_fn {
        ($name:ident -> $ret:ty) => {
            pub fn $name() -> $ret {
                Default::default()
            }
        };
    }
    
    /// Check if value matches pattern.
    #[macro_export]
    macro_rules! matches_pattern {
        ($val:expr, $pat:pat) => {
            matches!($val, $pat)
        };
    }
    
    /// Repeat a literal string.
    #[macro_export]
    macro_rules! repeat_lit {
        ($s:literal, $n:literal) => {
            $s.repeat($n)
        };
    }
    
    /// Time a block and return result.
    #[macro_export]
    macro_rules! timed {
        ($label:literal, $block:block) => {{
            let start = ::std::time::Instant::now();
            let result = $block;
            let elapsed = start.elapsed();
            (result, elapsed)
        }};
    }
    
    /// Execute statements with prefix/suffix.
    #[macro_export]
    macro_rules! with_setup {
        (setup: $setup:block, body: $body:block, teardown: $teardown:block) => {{
            $setup
            let result = $body;
            $teardown
            result
        }};
    }
    
    /// Create an enum from identifiers.
    #[macro_export]
    macro_rules! make_enum {
        ($name:ident { $($variant:ident),* $(,)? }) => {
            #[derive(Debug, Clone, Copy, PartialEq, Eq)]
            pub enum $name {
                $($variant,)*
            }
        };
    }
    
    make_enum!(Color { Red, Green, Blue });
    make_enum!(Status {
        Active,
        Inactive,
        Pending
    });
    
    /// A demo struct to show getter generation.
    pub struct Person {
        name: String,
        age: u32,
    }
    
    impl Person {
        pub fn new(name: &str, age: u32) -> Self {
            Person {
                name: name.to_string(),
                age,
            }
        }
    
        make_getter!(name: String);
        make_getter!(age: u32);
    }
    
    make_default_fn!(default_string -> String);
    make_default_fn!(default_vec -> Vec<i32>);
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_dbg_expr() {
            let v = dbg_expr!(2 + 3);
            assert_eq!(v, 5);
        }
    
        #[test]
        fn test_make_getter() {
            let p = Person::new("Alice", 30);
            assert_eq!(p.name(), "Alice");
            assert_eq!(*p.age(), 30);
        }
    
        #[test]
        fn test_make_default_fn() {
            assert_eq!(default_string(), "");
            assert_eq!(default_vec(), Vec::<i32>::new());
        }
    
        #[test]
        fn test_matches_pattern_some() {
            let opt: Option<i32> = Some(42);
            assert!(matches_pattern!(opt, Some(_)));
            assert!(!matches_pattern!(opt, None));
        }
    
        #[test]
        fn test_matches_pattern_none() {
            let opt: Option<i32> = None;
            assert!(matches_pattern!(opt, None));
            assert!(!matches_pattern!(opt, Some(_)));
        }
    
        #[test]
        fn test_repeat_lit() {
            assert_eq!(repeat_lit!("ab", 3), "ababab");
            assert_eq!(repeat_lit!("x", 5), "xxxxx");
        }
    
        #[test]
        fn test_timed() {
            let (result, _elapsed) = timed!("sum", { (1..=100).sum::<i32>() });
            assert_eq!(result, 5050);
        }
    
        #[test]
        fn test_make_enum() {
            assert_eq!(Color::Red, Color::Red);
            assert_ne!(Color::Red, Color::Blue);
            assert_eq!(Status::Active, Status::Active);
        }
    
        #[test]
        fn test_with_setup() {
            let mut counter = 0;
            let result = with_setup!(
                setup: { counter += 1; },
                body: { counter * 10 },
                teardown: { counter += 1; }
            );
            assert_eq!(result, 10);
            assert_eq!(counter, 2);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_dbg_expr() {
            let v = dbg_expr!(2 + 3);
            assert_eq!(v, 5);
        }
    
        #[test]
        fn test_make_getter() {
            let p = Person::new("Alice", 30);
            assert_eq!(p.name(), "Alice");
            assert_eq!(*p.age(), 30);
        }
    
        #[test]
        fn test_make_default_fn() {
            assert_eq!(default_string(), "");
            assert_eq!(default_vec(), Vec::<i32>::new());
        }
    
        #[test]
        fn test_matches_pattern_some() {
            let opt: Option<i32> = Some(42);
            assert!(matches_pattern!(opt, Some(_)));
            assert!(!matches_pattern!(opt, None));
        }
    
        #[test]
        fn test_matches_pattern_none() {
            let opt: Option<i32> = None;
            assert!(matches_pattern!(opt, None));
            assert!(!matches_pattern!(opt, Some(_)));
        }
    
        #[test]
        fn test_repeat_lit() {
            assert_eq!(repeat_lit!("ab", 3), "ababab");
            assert_eq!(repeat_lit!("x", 5), "xxxxx");
        }
    
        #[test]
        fn test_timed() {
            let (result, _elapsed) = timed!("sum", { (1..=100).sum::<i32>() });
            assert_eq!(result, 5050);
        }
    
        #[test]
        fn test_make_enum() {
            assert_eq!(Color::Red, Color::Red);
            assert_ne!(Color::Red, Color::Blue);
            assert_eq!(Status::Active, Status::Active);
        }
    
        #[test]
        fn test_with_setup() {
            let mut counter = 0;
            let result = with_setup!(
                setup: { counter += 1; },
                body: { counter * 10 },
                teardown: { counter += 1; }
            );
            assert_eq!(result, 10);
            assert_eq!(counter, 2);
        }
    }

    Deep Comparison

    OCaml vs Rust: Macro Fragment Types

    Fragment Specifiers

    DesignatorCapturesExample
    exprExpression2 + 3, foo()
    identIdentifiermy_var, MyType
    tyTypei32, Vec<String>
    patPatternSome(x), _
    literalLiteral"hello", 42
    blockBlock{ statements }
    stmtStatementlet x = 1;
    ttToken treeAnything

    Examples

    expr — Any expression

    macro_rules! dbg_expr {
        ($e:expr) => {
            println!("{} = {:?}", stringify!($e), $e);
        };
    }
    
    dbg_expr!(2 + 3 * 4);  // "2 + 3 * 4 = 14"
    

    ident — Identifiers for code generation

    macro_rules! make_getter {
        ($field:ident : $ty:ty) => {
            fn $field(&self) -> &$ty { &self.$field }
        };
    }
    
    struct Point { x: i32, y: i32 }
    impl Point {
        make_getter!(x: i32);
        make_getter!(y: i32);
    }
    

    ty — Type names

    macro_rules! make_default {
        ($name:ident -> $ret:ty) => {
            fn $name() -> $ret { Default::default() }
        };
    }
    
    make_default!(empty_vec -> Vec<i32>);
    

    pat — Patterns

    macro_rules! matches_pat {
        ($val:expr, $pat:pat) => { matches!($val, $pat) };
    }
    
    matches_pat!(Some(42), Some(_));  // true
    

    OCaml Equivalent

    OCaml uses ppx for metaprogramming:

    (* No direct equivalent to designators *)
    (* ppx_deriving generates code from attributes *)
    
    [@@deriving show, eq]
    type point = { x: int; y: int }
    

    5 Takeaways

  • **expr is most common — captures any expression.**
  • Works for math, function calls, variables.

  • **ident enables code generation.**
  • Generate getters, setters, function names.

  • **ty captures type annotations.**
  • Useful for generic macro-generated code.

  • **literal is more restrictive than expr.**
  • Only actual literals, not variables.

  • **tt is the escape hatch.**
  • Captures anything when other designators fail.

    Exercises

  • Property macro: Implement property!($name:ident : $ty:ty = $default:expr) that generates a struct field, a getter returning &$ty, a setter set_$name(&mut self, val: $ty), and a default value initializer.
  • Enum constructor: Write make_enum_from!(EnumName { Variant1(Type1), Variant2(Type2) }) that generates both From<Type1> for EnumName and From<Type2> for EnumName implementations.
  • Debug print with type: Create typed_dbg!($e:expr) that prints "{expr_text}: {type_name} = {value:?}". Use std::any::type_name::<T>() inside the expansion to show the inferred type name.
  • Open Source Repos