Skip to content

Postfix macro syntax for methods via opt-in attribute: obj.method!(...) → obj.method(MACRO!(...)) #3904

@OuYangPaste

Description

@OuYangPaste

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"), 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions