Macros

Attribute-Like Macros

An attribute-like procedural macro (#[proc_macro_attribute]) defines a custom outer attribute #[my_attribute(args)] that can be attached to any Rust item (functions, structs, enums, impl blocks, traits, or modules) and replaces that item with the TokenStream returned by the macro.

Signature of an Attribute-Like Macro

Unlike a derive macro (which takes 1 TokenStream and appends to the item), an attribute-like macro takes two TokenStream parameters and replaces the annotated item:

// Inside a `proc-macro = true` crate:
#[proc_macro_attribute]
pub fn trace_call(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream
  1. attr: The tokens inside the attribute's parentheses—for example, in #[route(GET, "/users")], attr contains GET, "/users". If invoked with no parentheses (#[tokio::main]), attr is empty.
  2. item: The complete token stream of the annotated item (such as fn index() -> Html { ... }), excluding the #[route(...)] attribute itself.
#[trace("db")]                   Attribute macro receives:
fn query(id: u32) -> bool {        attr = `"db"`
    id > 0                         item = `fn query(id: u32) -> bool { id > 0 }`
}                                  │
                                   ▼ Replaces original `fn query` with wrapped version!

How Attribute Macros Transform Functions (e.g., #[tokio::main] or Tracing Wrappers)

When an attribute macro wraps a function (such as #[tokio::main] turning async fn main() into a synchronous fn main() that builds an async runtime, or a tracing macro logging entry and exit), it parses the input ItemFn, keeps the function's signature, and wraps its block body:

// Source written by the developer with a `#[trace_fn("AUTH")]` attribute macro:
/*
#[trace_fn("AUTH")]
fn verify_token(token: &str) -> bool {
    token.starts_with("valid_")
}
*/

// Code emitted by `#[trace_fn("AUTH")]` replacing `verify_token`:
fn verify_token(token: &str) -> bool {
    println!("[AUTH] entering verify_token");
    let __result = (|| token.starts_with("valid_"))();
    println!("[AUTH] exiting verify_token -> {__result:?}");
    __result
}

fn main() {
    let ok = verify_token("valid_9912");
    println!("Caller received: {ok}");
}
[AUTH] entering verify_token
[AUTH] exiting verify_token -> true
Caller received: true

(Notice the inner closure (|| { ... })() in the expanded function: wrapping the original function body in an immediately invoked closure ensures that any early return statements inside the original body return from the closure rather than skipping the exit log!)

Derive Macro vs. Attribute-Like Macro

Feature Derive Macro (#[derive(Foo)]) Attribute-Like Macro (#[foo])
Valid targets struct, enum, union only Any item (fn, struct, impl, trait, mod, extern)
Can modify or remove fields/body of target item? No (strictly additive) Yes (replaces target item completely)
Accepts arguments directly on attribute? Only via separate helper attributes Yes (#[route(GET, "/api")])