Notice the peculiar bracket types I used here: curly braces around parameters, square brackets around the code block, and all types of brackets in the invocation.
There's no restriction to which bracket types I can use. In the macro definition, instead of {} => [ I could have written [] => (, () => {, [] => [, {} => (, etc.
I stumbled upon this language property today and I wonder what the benefit of such loose bracket type checking is. To me, this seems inconsistent with the rest of the language:
In Rust, code block statements are always surrounded with curly braces (reference). Macro definitions are the only exception
In Rust, functions/collection literals/control constructs always use paranthesis, square brackets and curly braces, respectively.
It's my_function(); not my_function[] or my_function{}.
It's let arr = [1, 2, 3], not let arr = {1, 2, 3} or let arr = (1, 2, 3).
It's loop {}, not loop () or loop [].
However, in macros, you can use whatever.
vec![1, 2, 3] is just as valid as vec!(1, 2, 3) and vec!{1, 2, 3}.
lazy_static! { } is just as valid as lazy_static! [ ] and lazy_static! ( )
It seems like, again, macros are the only exception to Rust's usual consistency.
I wonder what the reasoning behind these decisions are. Is there a benefit to vec!{1, 2, 3} instead of vec![1, 2, 3]? Is there a benefit to allowing any bracket type in macro bodies (i.e. after the =>), instead of just standardizing the curly braces?
One of the people who I have already asked about this guessed that this was an early spontaneous design decision, and it was kept because of backwards compatibility concerns. Is this true?
Well. there is one subtle difference with different brackets on the call site, described here: Writing Python inside your Rust code — Part 4 - Mara's Blog
In short (if I understand correctly): in statement positions, macro calls with curly braces are parsed as statements, while other macro calls are parsed as expressions (i.e. only a part of statement), which allows for some compile-time tricks.
Other than that, this is likely a early design decision. I can, however, understand it: for me, it's much more natural, for example, to write vec![1, 2, 3] then vec!(1, 2, 3), when we have similar syntax for arrays; on the other hand, print!("{}", val) is like a function call and will look clumsy as print!["{}", val]; and macros expanding to items, like lazy_static!, are naturally associated with blocks.
Oh yeah, I've seen that subtle difference before. It's really only a subtle difference though; bracket types are still very loose.
I think I was misunderstood in the post:
for me, it's much more natural, for example, to write vec![1, 2, 3] then vec!(1, 2, 3) , when we have similar syntax for arrays; on the other hand, print!("{}", val) is like a function call and will look clumsy as print!["{}", val] ; and macros expanding to items, like lazy_static! , are naturally associated with blocks.
I'm of the exact same opinion here. I'm of the opinion that macros should use the appropriate bracket type depending on where they're used in. Furthermore, I'm of the opinion that macro invocation should only use the appropriate bracket type - not be allowed to use whichever bracket type the user deems better: because why should we want vec!()/vec!{} or lazy_static![]/lazy_static!()?
Basically, this is how I would expect the macro system to work if I had no prior experience with Rust's specific implementation of it:
// v (1) Curly braces here...
macro_rules! my_macro {
// v v (3) **arbitrary** bracket type here...
( <args ) => { // <-- (2) curly braces here...
} // <-- (2) ...and here
} // <-- (1) ...and here
// NOTE!!! In the macro invocation, the same bracket type would need be used as in the definition:
my_macro!()
// ^^ (3) ...same as above
Why would I do it like this?
Curly braces around the macro definition - Rust already works like this today, which I find entirely logical. Curly braces should encode code blocks and control constructs, and they do here.
I would also put curly braces around the code blocks in the macro definitions, instead of allowing arbitrary bracket types. For one, this would be consistent with the language's usual use of curly braces. And note that with this I'm not forcing macros to be invoked with curly braces always! The bracket type around the macro definition body is an implementation detail and has no impact whatsoever, so it should be standardized to the obvious choice - curly braces.
I would enforce the bracket types used in macro invocation to be the same as written in the macro definition. It seems logical to me - if the macro author used square brackets around the parameters in the macro definition, the macro should be used with square brackets in its invocation too.
I would really love to hear some concrete points made against (2) and (3). As far as I can tell, there are no drawbacks whatsoever (apart from backwards-compatibility; code like vec!() or lazy_static![] or println!{} wouldn't work anymore)
Also, my idea wouldn't keep macro authors from doing this. In fact, it allows them more precise control. For example, if someone writes a macro to initialize arrays, and they want it to work both with curly braces and square brackets, they could just do this
The best place to look for this sort of historical discussion is in the RFCs. #378 puts in place the expression/statement interpretation of macro brackets, so it’s probably a good place to start.
Alright so to finally answer my own question: nobody in here knows. I will have to do my own research to find whether there were discussions about this topic, and why exactly this loose approach was decided upon.
Good luck! Please let us know the outcome of your research.
I suspect that this was simply a decision by Graydon Hoare, the original creator of Rust. If so, the pre-decision conversations may all have been in his head.
Some early changes to the macro syntax were proposed in the old wiki in mid-2011. Unfortunately I can't find any record of contemporaneous discussions of this proposal, aside from one brief mention in the rust-dev mailing list archives.
pauls: also will want to change the delimeters from [] to {} I believe, not possible with regular expressions
pauls: proposal is ident!( expr* ){tt} where the parenthesized exprs and the token trees are optional, so fmt would become fmt!("foo", bar, zed)
pauls: this allows for three forms
function-call like: fmt!(...)
exotic: let!{...}
block-like: for!(...) { ... }
graydon: feels to me like simplifying expression list all the way down to token trees with a comma-separated expression list as a particular pattern... it doesn't feel like it's that important to me to have a form that parses expressions ahead of time vs the complexity of multiple invocation forms, we could then allow arbitrary delimeters (fmt![] etc)
pauls: the simplicity of that appeals to me
eholk: current system allows () or []
graydon: this seems undesirable
pauls: was intended to be a temporary thing
graydon: best seems to me to be that each macro has a particular delimeter chosen by the macro author, just from a simplicity perspective of explaining this to the user
But it seems the current system of flexible delimiters was fully implemented shortly after this, and this line of discussion was basically dropped. (Perhaps the situation we ended up in was “good enough” that it never became again became a high-priority issue before the push to 1.0.)
As someone who experience of writing Rust macros is almost zero it did seem a bit odd to allow various bracketing, but at the end of the day does it actually cause any harm or confusion?