R
RUSTY YELLOW PAGES

#[proc_macro] / #[proc_macro_derive(...)] / #[proc_macro_attribute]Attribute


Explanation

These three attributes register an ordinary function as a specific kind of procedural macro. Each requires the crate it's defined in to declare proc-macro = true under [lib] in Cargo.toml; such a crate may export only functions tagged with one of these three (plus private helper items) — no ordinary public functions or types alongside them. The function itself must be pub and return TokenStream.

  • #[proc_macro] — registers a function-like macro. Signature: fn(TokenStream) -> TokenStream. Invoked at the call site as name!(...), the same syntax as a macro_rules! macro. See Function-like macros for what this kind is for.
  • #[proc_macro_derive(TraitName)] — registers a derive macro as the implementation behind #[derive(TraitName)]. Signature: fn(TokenStream) -> TokenStream. Optionally takes a second argument, attributes(helper_one, helper_two, ...), which reserves one or more otherwise-inert helper attribute names (e.g. #[proc_macro_derive(Reading, attributes(unit))] reserves #[unit(...)]) as legal to write inside the annotated item, for the derive function itself to inspect via its TokenStream input. See Derive macros.
  • #[proc_macro_attribute] — registers an attribute-like macro. Signature: fn(TokenStream, TokenStream) -> TokenStream — the attribute's own arguments, then the full annotated item. Invoked as #[name] or #[name(...)] above an item. See Attribute-like macros.

A given function may carry only one of these three; a single proc-macro = true crate is free to export any mix of them, each its own separately tagged function. This page covers only the registration syntax itself — see Procedural macros for the shared token-stream-in/token-stream-out mental model, and the kind-specific concept pages above for what each macro kind is actually for and how it's used well.

Usage examples

Registering all three procedural macro kinds

use proc_macro::TokenStream;

#[proc_macro] // <- registers a function-like macro, invoked as `build!(...)`
pub fn build(input: TokenStream) -> TokenStream { input }

#[proc_macro_derive(Reading)] // <- registers a derive macro, invoked as `#[derive(Reading)]`
pub fn derive_reading(input: TokenStream) -> TokenStream { TokenStream::new() }

#[proc_macro_attribute] // <- registers an attribute-like macro, invoked as `#[traced]`
pub fn traced(_attr: TokenStream, item: TokenStream) -> TokenStream { item }

Designing a public API

A macro crate for a sensor-data library offers all three forms from one proc-macro = true crate: a derive for boilerplate trait impls, an attribute for wrapping handler functions, and a function-like macro for validated literals — each function tagged with the registration attribute matching what it needs to receive.

// sensorkit-macros/Cargo.toml
// [lib]
// proc-macro = true

use proc_macro::TokenStream;

#[proc_macro_derive(Reading, attributes(unit))] // <- also reserves the inert helper attribute #[unit(...)]
pub fn derive_reading(input: TokenStream) -> TokenStream {
    TokenStream::new() // a real implementation inspects `input`'s fields here
}

#[proc_macro_attribute] // <- receives the attribute's own args AND the annotated item, as two streams
pub fn traced(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

#[proc_macro] // <- receives only the tokens written inside its own `!(...)` invocation
pub fn sql(input: TokenStream) -> TokenStream {
    input // a real implementation validates the embedded SQL text here
}

Each registration attribute fixes the function's exact signature and what it's invoked as, so choosing between them is really choosing which of the three token-stream contracts a given macro needs — the Rust Reference's procedural macros chapter documents all three signatures, and a single proc-macro = true crate is free to mix any number of each, since the registration is per function, not per crate.

Explanation

Embedded support: Full

Nothing about #[proc_macro]/#[proc_macro_derive(...)]/#[proc_macro_attribute] themselves changes for embedded targets, because a procedural macro never runs on the target at all — it's a plain function compiled for, and executed on, the host machine as part of the compiler's own build process, taking token streams in and producing token streams out. This host/target split is the same one doctests run into: the macro crate itself has no #![no_std] concerns, no target triple, and no access to (or need for) any peripheral — its only job is to transform source text before the target build ever begins.

What is embedded-specific is what real embedded proc-macros generate: cortex-m-rt's #[entry] is an attribute-like macro (#[proc_macro_attribute]-backed) that runs on the host at compile time and rewrites the annotated function into the shape the reset handler expects to call, wiring it up as the firmware's actual entry point; RTIC's #[app] is a much larger attribute-like macro that expands an entire annotated module into the interrupt-driven scheduling, resource-locking, and task-dispatch code RTIC applications run on. Both only ever produce ordinary #![no_std]-compatible Rust source — the macro's own execution is host-side and completely unaffected by the target it's generating code for.

Usage examples

cortex-m-rt's #[entry] transforming code at compile time, on the host

#![no_std]
#![no_main]

use cortex_m_rt::entry; // <- #[proc_macro_attribute]-backed: runs on the host at compile time

#[entry] // <- rewrites this function into the firmware's actual entry point; the rewritten code runs on-target
fn main() -> ! {
    loop {}
}

RTIC's #[app] expanding a whole module into scheduling code

#[rtic::app(device = pac)] // <- proc-macro-attribute expansion happens on the host; only its output ships to the chip
mod app {
    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    #[init]
    fn init(_cx: init::Context) -> (Shared, Local) {
        (Shared {}, Local {})
    }
}