Postfix macro syntax for methods via opt-in attribute: obj.method!(...) → obj.method(MACRO!(...))
I’d like to propose a small but meaningful ergonomic improvement that bridges Rust’s powerful macro system with its object-oriented method syntax—without blurring their responsibilities.
The idea is simple: allow a method to be annotated with `#[postfix_macro(M)]`, so that calling `obj.method!(args...)` automatically expands to `obj.method(M!(args...))` at parse time.
Crucially:
- The macro ` M ` remains a completely ordinary, free-standing macro. It could be `format_args!`, `serde_json::json!`, or any user-defined macro. It is not tied to the type, doesn’t need special awareness of `self`, and can be used anywhere else in the codebase.
- The method remains a normal Rust method: it has a regular signature, shows up in documentation, can be called directly (e.g., `logger.info(my_preformatted_args)`), and contains all the real logic—like checking log levels, writing to a buffer, or validating state.
In other words: **the macro handles compile-time argument preprocessing; the method handles runtime behavior**. They stay separate in definition, but combine naturally at the call site.
Why does this matter? Consider logging:
// Today
info!(logger, "user {} connected", name);
// With this proposal
logger.info!("user {} connected", name);
The second version reads more fluently because the action (`info!`) is attached to its context (`logger`). Yet under the hood, nothing magical happens: it’s just `logger.info(format_args!(...))`.
This pattern applies beyond logging—to serialization (`obj.serialize!( { x: 1 } )`), validation, GPU kernels, and more. All these cases share the same structure: *a macro processes arguments at compile time, and a method acts on them at runtime*.
### How this differs from RFC #2442
This is **not** a revival of [RFC #2442 ("Postfix macros")](https://github.com/rust-lang/rfcs/pull/2442), which proposed allowing *any* macro to be called in postfix position (e.g., `expr.any_macro!(...)`). That approach was rightly rejected because it:
- Created ambiguity between methods and macros;
- Allowed arbitrary macros to “attach” to any type without the type author’s consent;
- Risked namespace pollution and hidden coupling.
In contrast, this proposal is **explicit, opt-in, and scoped**:
- Only methods explicitly annotated with `#[postfix_macro]` gain the `!` syntax;
- The macro is chosen by the *type author*, not the caller;
- No new naming rules or resolution semantics are introduced.
It’s less about “postfix macros” and more about **giving method authors a way to offer a macro-powered shorthand**—while keeping full control.
### Safety & minimalism
- Fully backward compatible;
- Requires only a parser-level desugaring (like `?`);
- Zero runtime cost;
- No changes to Rust’s macro or trait systems.
This isn’t about making macros “look like methods.”
It’s about letting each do what it’s best at—and compose them cleanly where it matters most: in user code.
5 条评论