An attribute macro is a Rust procedural macro that defines a custom attribute such as #[public] and replaces the item it annotates with generated tokens. It can annotate structs, enums, unions, traits, or functions, so it can change existing definitions rather than merely add to them. section 1.3.1 section 4.10
Its entry point is marked #[proc_macro_attribute] and receives two TokenStreams: attribute arguments and the annotated item. Returning an empty token stream removes that item entirely, making replacement behavior central to both implementation and debugging. section 4.2
A practical implementation follows this path:
- Parse the item into a syn AST, often DeriveInput for structs, enums, and unions, or a more specific item type when needed. section 4.4
- Inspect the relevant AST nodes, such as DataStruct and named Fields, then preserve or alter the parts the macro owns. section 4.4
- Generate replacement tokens with quote!, recreating the item and any changed fields, methods, or declarations. section 4.10 section 10.2
- Parse optional attribute arguments from the first TokenStream when custom configuration is needed. section 8.7
Making selected struct fields public
#[public(exclude(fourth, third))] can transform a struct so unexcluded fields become pub while excluded fields retain their original visibility. The macro parses the exclude list, walks the struct fields, and quotes a replacement struct. section 8.7 section 4.4
Choose an attribute macro when code must be altered. Prefer a derive macro when only adding functionality to a struct, enum, or union; use a function-like macro when transformation must apply more broadly. section 5.4
Attribute arguments are flexible and active: the macro can accept custom syntax in its annotation, and the processed annotation removes itself. This enables configurable transformations, but also makes clear parsing and useful compile errors important. section 8.11 section 7.0
Listing 4.3 Initial setup
extern crate core;
use quote::quote;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_attribute] #1
pub fn public(_attr: TokenStream, item: TokenStream) #2
-> TokenStream {
let _ast = parse_macro_input!(item as DeriveInput); #3
let public_version = quote! {}; #4
public_version.into()
}
Inspect the minimal #[proc_macro_attribute] setup to see the two inputs, AST parsing, and why an empty quoted output makes the annotated struct disappear. section 4.2

