Search code examples
rustmacrosmetaprogramming

Is there a systematic method for simplifying or reducing macro_rules?


I want to emulate capture rules in cpp using a macro as a learning exercise, and I realized that rustfmt doesn't always play nice. I also am wondering if there is a way of simplifying or using particular techniques to reduce the complexity of macro rules. Like if there is a standard form for macro_rules.

Here is the macro in question:

macro_rules! capture {
    ([$($tail:tt)*] $expression:expr) => (
        {
            capture![$($tail)*];
            $expression
        }
    );
    [mut $value:ident, $($tail:tt)*] => {
        let mut $value = $value.clone();
        capture![$($tail)*];
    };
    [mut $value:ident] => {
        let mut $value = $value.clone();
    };

    [$value:ident, $($tail:tt)*] => {
        let $value = $value.clone();
        capture![$($tail)*];
    };
    [$value:ident] => {
        let $value = $value.clone();
    };

    [$name:tt = $value:expr, $($tail:tt)*] => {
        let $name = $value;
        capture![$($tail)*];
    };
    [$name:tt = $value:expr] => {
        let $name = $value;
    };

    [mut $name:tt = $value:expr, $($tail:tt)*] => {
        let mut $name = $value;
        capture![$($tail)*];
    };
    [mut $name:tt = $value:expr] => {
        let mut $name = $value;
    };
}

which may be used like the following:

#[test]
fn test_capture() {
    let a = String::from("hello");
    let b = String::from("world");

    let x = capture!([mut a, b, c = 42] move || a.clone() + &b + &c.to_string() );

    let _ = a;
    let _ = b;

    let _ = x();
    let _ = x();

    println!("{:?}", x());
}

I tried to reduce the macro rules by creating a comma separated list, similar to the vec macro, but the vec macro takes a homogenous list of expr I believe which makes the problem simplier. In contrast my problem wants to be able to accept different kinds of token patterns, but it causes a lot of boilerplate.


Solution

  • One thing you can do is avoid duplicating your tailed and non-tailed rules via a zero-or-one repetition: matching on $(...)? and expanding with $(...)*. That at least reduces your rules into only the major forms of your DSL:

    macro_rules! capture {
        ([$($tail:tt)*] $expression:expr) => (
            {
                capture![$($tail)*];
                $expression
            }
        );
        [mut $value:ident $(, $($tail:tt)*)?] => {
            let mut $value = $value.clone();
            $(capture![$($tail)*];)*
        };
        [$value:ident $(, $($tail:tt)*)?] => {
            let $value = $value.clone();
            $(capture![$($tail)*];)*
        };
        [$name:tt = $value:expr $(, $($tail:tt)*)?] => {
            let $name = $value;
            $(capture![$($tail)*];)*
        };
        [mut $name:tt = $value:expr $(, $($tail:tt)*)?] => {
            let mut $name = $value;
            $(capture![$($tail)*];)*
        };
    }
    

    As for something more "systematic"... eh no, not really. Not that I can think of. You can check out the tracing::event macro to see it has many very similar arms as well.