Macros
Derive Macros
A derive macro (#[proc_macro_derive(TraitName)]) is a procedural macro invoked via #[derive(TraitName)] on a struct, enum, or union that inspects the type's name, generics, and fields and emits additional items (typically an impl TraitName for TypeName block) immediately following the original type definition.
Key Properties of Derive Macros
- Additive only: A derive macro never modifies or removes the original
structorenum; its returnedTokenStreamis appended after the annotated item. - Helper attributes (
attributes(...)): A derive macro can declare inert helper attributes (such as#[serde(rename = "id")]or#[default]) using#[proc_macro_derive(MyTrait, attributes(my_helper))]so fields and variants can customize the generated implementation.
#[derive(Describe)] Original item kept intact:
struct Server { ──> struct Server { host: String, port: u16 }
host: String, ──> Appended by derive macro:
port: u16, impl Describe for Server { ... }
}
Implementing a Custom Derive Macro (proc-macro Crate + Consumer)
In the procedural macro crate (lib.rs with proc-macro = true):
// Inside a `proc-macro = true` crate (e.g., `describe_derive/src/lib.rs`):
/*
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(TypeName)]
pub fn derive_type_name(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics TypeName for #name #ty_generics #where_clause {
fn type_name() -> &'static str {
stringify!(#name)
}
}
};
TokenStream::from(expanded)
}
*/
// What the derive macro expands to in the consumer crate:
trait TypeName {
fn type_name() -> &'static str;
}
struct ConnectionPool<T> {
_marker: std::marker::PhantomData<T>,
}
// Generated by `#[derive(TypeName)]`:
impl<T> TypeName for ConnectionPool<T> {
fn type_name() -> &'static str {
stringify!(ConnectionPool)
}
}
fn main() {
println!("Derived type name: {}", ConnectionPool::<u32>::type_name());
}
Derived type name: ConnectionPool
Handling Generics Correctly (split_for_impl)
When writing a derive macro, a user's struct may have lifetime parameters, type parameters, const generics, and where clauses (struct Wrapper<'a, T: Clone, const N: usize> where T: Debug). Calling ast.generics.split_for_impl() in syn splits the generics into three pieces required for the impl header:
impl_generics:<'a, T: Clone, const N: usize>(placed right afterimpl)ty_generics:<'a, T, N>(placed right after the struct name)where_clause:where T: Debug(placed right before the opening{)