Search code examples
macrosrustrust-macros

Why are macros based on abstract syntax trees better than macros based on string preprocessing?


I am beginning my journey of learning Rust. I came across this line in Rust by Example:

However, unlike macros in C and other languages, Rust macros are expanded into abstract syntax trees, rather than string preprocessing, so you don't get unexpected precedence bugs.

Why is an abstract syntax tree better than string preprocessing?


Solution

  • If you have this in C:

    #define X(A,B) A+B
    int r = X(1,2) * 3;
    

    The value of r will be 7, because the preprocessor expands it to 1+2 * 3, which is 1+(2*3).

    In Rust, you would have:

    macro_rules! X { ($a:expr,$b:expr) => { $a+$b } }
    let r = X!(1,2) * 3;
    

    This will evaluate to 9, because the compiler will interpret the expansion as (1+2)*3. This is because the compiler knows that the result of the macro is supposed to be a complete, self-contained expression.

    That said, the C macro could also be defined like so:

    #define X(A,B) ((A)+(B))
    

    This would avoid any non-obvious evaluation problems, including the arguments themselves being reinterpreted due to context. However, when you're using a macro, you can never be sure whether or not the macro has correctly accounted for every possible way it could be used, so it's hard to tell what any given macro expansion will do.

    By using AST nodes instead of text, Rust ensures this ambiguity can't happen.