Procedural macros for custom derives and attributes

Marcus Chen Jan 2026
1 tab
// In a proc-macro crate:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyTrait)]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    let expanded = quote! {
        impl MyTrait for #name {
            fn my_method(&self) {
                println!("MyTrait for {}", stringify!(#name));
            }
        }
    };
    TokenStream::from(expanded)
}
1 file · rust Explain with highlit

Procedural macros operate on Rust syntax trees, enabling custom #[derive(...)], attribute macros, or function-like macros. I write proc macros for boilerplate reduction: auto-generating builders, serialization, or validation. They're more powerful than declarative macros but require a separate crate with proc-macro = true. The syn crate parses Rust code, quote generates code, and proc-macro2 bridges the APIs. Proc macros run at compile time and can introspect types. I use them to enforce invariants, generate test cases, or create DSLs. They're the foundation of serde's derive macros and many popular crates. Writing them is advanced but worthwhile for widely-used patterns.