macro_rules_attribute-rs
Apply declarative macro_rules! macros as attribute or derive syntax in Rust, without the proc-macro crate overhead.
Repository Health
Technical Analysis
macro_rules_attribute is a small, no_std, forbid(unsafe_code) Rust crate that bridges the ergonomics gap between macro_rules! macros and proc_macro_attribute/derive syntax. Normally, decorating an item with a macro_rules! macro means wrapping the whole item inside the macro invocation (foo! { struct S { .. } }), which causes rightward drift and doesn’t compose across multiple macros. Writing a real proc-macro crate fixes the ergonomics but pulls in syn, quote, and proc-macro2, adding real compile-time cost to a project.
This crate closes that gap: its #[apply(macro_name!)] and #[derive(MacroName!)] attributes let any existing macro_rules! macro be invoked using attribute-position or derive-position syntax, with the item text passed through untouched as macro input. It also ships derive_alias! and attribute_alias! helpers for defining reusable shorthand groups (e.g. #[derive(StdDerives!)] expanding to several #[derive(..)] groups), which is a common ergonomics win in codebases with repeated derive stacks.
Internally the crate is split into a thin proc-macro backend (macro_rules_attribute-proc_macro, a small workspace member) and a no_std frontend crate that re-exports its attributes with documentation. It depends only on pastey at runtime, keeping downstream compile times low, which is the crate’s whole reason for existing: give macro_rules! authors attribute-like call sites without forcing consumers to compile syn/quote.
What You Get
#[apply(macro_name!)]to invoke anymacro_rules!macro using attribute syntax instead of wrapping the whole item#[derive(MacroName!)]/#[macro_rules_derive(...)]to invokemacro_rules!macros in derive position, with read-only access to the decorated itemderive_alias!for defining shorthand names that expand to a group of standard or custom derivesattribute_alias!for defining shorthand names that expand to arbitrary attribute groups (e.g. complexcfg(...)expressions)no_stdsupport and aforbid(unsafe_code)guarantee, keeping the crate itself minimal and auditable- an optional
verbose-expansionsfeature that prints each macro invocation’s exact expansion at compile time for debugging
Common Use Cases
- Applying a
-litepolyfill macro (e.g.pin-project-lite’spin_project!) using attribute syntax instead of its native wrapping syntax - Defining project-wide derive shorthands like
#[derive(StdDerives!)]that expand to a consistent set of standard trait derives - Writing custom derive-like macros with plain
macro_rules!when a full proc-macro crate’s compile-time cost isn’t justified - Building small, dependency-light libraries that need attribute-like macro ergonomics without adding
syn/quote/proc-macro2to the dependency graph - Composing multiple independent
macro_rules!decorators on the same item, which isn’t possible with the traditional item-wrapping macro syntax
Under The Hood
Architecture
The crate is split into two parts: a no_std, forbid(unsafe_code) frontend (src/lib.rs) that defines and documents the public attributes (apply, derive, derive_alias!, attribute_alias!), and a thin proc-macro backend crate (src/proc_macro/mod.rs, a separate workspace member macro_rules_attribute-proc_macro) that implements macro_rules_attribute and macro_rules_derive as #[proc_macro_attribute] functions. The backend’s job is narrowly mechanical: validate that the attribute argument is a path!-style macro reference, then rewrite the token stream into a direct invocation of that macro with the decorated item as its argument (wrapped in a brace group), letting the referenced macro_rules! macro do all the real code generation. macro_rules_derive additionally splits comma-separated derive arguments and appends a marker derive to suppress unknown-derive-helper-attribute errors. Because logic beyond token-stream reshaping lives entirely in the macro_rules! macros being invoked, the abstraction that would break if changed is this attribute-to-invocation rewriting step itself.
Tech Stack
Pure Rust, edition 2018, MSRV 1.78.0. Runtime dependency is limited to pastey (~0.2) for the frontend crate; the proc-macro crate depends on the standard proc-macro crate plus core iterator/ops traits, deliberately avoiding syn, quote, and proc-macro2 to keep compile times low for downstream consumers. Dev-dependencies (once_cell, pin-project-lite, serde) exist only to exercise real-world macro composition scenarios in doctests and integration tests. CI (GitHub Actions) checks against MSRV, stable, and beta across Linux/macOS/Windows, with both --locked and freshly-updated lockfile runs.
Code Quality
Testing leans heavily on Rust’s doctest system — the frontend’s extensive rustdoc comments embed runnable and compile_fail examples that double as both documentation and regression tests, supplemented by two small integration test files (tests/custom_derive.rs, tests/inert_derive_helpers.rs) exercising derive-alias and inert-attribute-helper behavior. There is no separate unit-test suite for the proc-macro backend beyond these doctests/integration tests; correctness of token-stream rewriting is instead implicitly checked by every doctest compiling successfully. Naming is explicit and the frontend crate forbids unsafe code entirely; the backend crate, working directly with proc_macro::TokenStream, necessarily has more manual token-tree handling. CI runs cargo check and cargo test across multiple toolchains and platforms, which is a reasonable quality bar for a macro-only crate of this size.
What Makes It Unique
Rather than building a new macro system, this crate solves a narrow, specific ergonomics problem: giving macro_rules! — which is compile-time cheap but syntactically clunky at call sites — the attribute/derive-position ergonomics normally reserved for proc-macros, without paying proc-macro’s syn/quote compile-time tax. Its derive_alias!/attribute_alias! helpers for composing reusable macro groups are a genuinely useful pattern that most equivalent crates (e.g. heavier proc-macro-based derive-alias solutions) don’t offer as cheaply. It isn’t inventing new macro capabilities, but it meaningfully lowers the cost of a common Rust ergonomics workaround.