ident!(...) / ident!{...} / ident![...]Macro
Explanation
Every macro invocation — declarative or procedural, function-like or a
macro_rules!-style call — is a path, a !, and one delimited group of
tokens. That group may be wrapped in (...), [...], or {...}. To the
compiler these three are 100% interchangeable: vec!(1, 2, 3),
vec![1, 2, 3], and vec!{1, 2, 3} all parse identically and expand
identically. This page is about that choice of bracket; see
! for the marker itself and what
makes something a macro invocation in the first place.
Since the delimiters carry no grammatical meaning, the choice between them is pure convention, guided by what the expansion reads like:
(...)— for macros that read like a function call or produce a single expression:println!("..."),format!(...),assert_eq!(a, b),matches!(value, Pattern).[...]— for macros that produce something list- or array-like:vec![1, 2, 3]is the standard example, echoing the[1, 2, 3]array literal syntax it's building on top of.{...}— for macros whose expansion is one or more whole items or statements rather than a single value:macro_rules! name { ... }itself, and item-producing macros like std'sthread_local! { ... }.
The one place the choice has an actual (if narrow) grammatical
consequence: when a macro invocation is used as a statement or item, a
{...}-delimited invocation does not require a trailing ; — the
closing brace already ends it, the same rule that lets a fn or impl
block go without one. A (...)- or [...]-delimited invocation used as
a statement still needs the trailing ; (println!("ready");).
Usage examples
Equivalent delimiters for a list-like macro
let a = vec!(1, 2, 3); // <- legal: () instead of the conventional [] for this list-like macro
let b = vec![1, 2, 3]; // <- idiomatic form for vec!, matching array-literal syntax
assert_eq!(a, b); // <- both invocations expand identically
Designing a public API
A metrics module defines a thread-local counter with std's
thread_local! macro, which expands into full item definitions — so, by
convention, it's written with {...} and, like any other item, needs no
trailing semicolon.
use std::cell::RefCell;
thread_local! { // <- {} form: this macro's expansion is item(s), not a single value
static REQUEST_COUNT: RefCell<u32> = RefCell::new(0);
} // no trailing `;` needed here, exactly like a `fn` or `impl` block
fn record_request() {
REQUEST_COUNT.with(|count| *count.borrow_mut() += 1); // <- () form: reads like a method call
}
The compiler would accept any of the three delimiter
pairs here, but {...} is the idiomatic signal that a macro's expansion
is itself one or more items rather than a value — the same visual cue an
ordinary mod or impl block gives, which is why
thread_local!
and macro_rules! itself are always written this way in practice.