Search code examples
rustrust-macros

Double vs single bracket in rust macros


I've been looking at rust macros recently and have found conflicting examples of macros using brackets (as laid out below). I'd like to know what the difference between each of these are and which one should be used when building macros. I'd also like to know whether any docs exist for any of this, as I can't find anything on the interwebs.

macro_rules! mac_a {
    ($x:ident,$y:expr) => { // <-- outer curlies
        { // <-- inner curlies
            let $x = $y;
            println!("{} {}", $x, $y);
        }
    };
}

macro_rules! mac_b {
    ($x:ident,$y:expr) => { // <-- outer curlies
        // <-- no inner brackets / curlies
        let $x = $y;
        println!("{} {}", $x, $y);
    };
}

// Does not compile
// macro_rules! mac_c {
//     ($x:ident,$y:expr) => ( // <-- outer brackets
//         ( // <-- inner brackets
//             let $x = $y;
//             println!("{} {}", $x, $y);
//         )
//     );
// }

macro_rules! mac_c2 {
    ($x:expr,$y:expr) => ( // <-- outer brackets
        ( // <-- inner brackets
            println!("{} {}", $x, $y)
        )
    );
}

macro_rules! mac_d {
    ($x:ident,$y:expr) => ( // <-- outer brackets
        // <-- no inner brackets / curlies
        let $x = $y;
        println!("{} {}", $x, $y);
    );
}

fn main() {
    mac_a!(a, 1);
    mac_b!(b, 2);
    // mac_c!(c, 3); // Does not compile
    mac_c2!(3, 3);
    mac_d!(d, 4);
}

All of the above except mac_c compile, and there are differences between each hence the need for mac_c2 with ident and let removed. I don't know why they can't be included ¯\_(ツ)_/¯


Solution

  • AFAIK the outer curlies/brackets are equivalent and simply serve to delimit each individual macro expansion. OTOH the inner curlies/brackets are part of the generated code and their contents must therefore be legal for where they are used. If we expand the macro invocations in your main functions, we get:

    fn main() {
        // mac_a!(a, 1);
        { // <-- inner curlies
            let a = 1;
            println!("{} {}", a, 1);
        }
    
        // mac_b!(b, 2);
        // <-- no inner brackets / curlies
        let b = 2;
        println!("{} {}", b, 2);
    
        // mac_c!(c, 3); // Does not compile
        ( // <-- inner brackets
            let c = 3;   // Invalid code
            println!("{} {}", c, 3);
        )
    
        // mac_c2!(3, 3);
        ( // <-- inner brackets
            println!("{} {}", 3, 3)
        )
    
        // mac_d!(d, 4);
        // <-- no inner brackets / curlies
        let d = 4;
        println!("{} {}", d, 4);
    }
    

    Note BTW that there is therefore a difference for what variables are still around after the macro invocations:

    fn main() {
        mac_a!(a, 1);
        mac_b!(b, 2);
        // mac_c!(c, 3); // Does not compile
        mac_c2!(3, 3);
        mac_d!(d, 4);
        
        // println!("{}", a); // Does not compile because the `let a = ...` was done inside curlies
        println!("{} {}", b, d); // Work because `let b = ...` and `let d = ...` were inserted in the current scope
    }
    

    Playground