Course contentsShow
← Rust
Lesson 1572 of 1,91013. Metaprogramming with MacrosFree lesson

Modular Macro Library Design

You will organize your macro logic into reusable modules that prioritize compilation speed and cacheability.

In this lesson, you will learn how to structure complex procedural macro crates into decoupled, cacheable modules to prevent compilation bottlenecks.

When building expansive macro systems—like the ones we’ve explored for automatic mocking or type-safe SQL—a common trap is monolithic design. Placing all TokenStream manipulation logic into a single lib.rs file forces the compiler to re-process the entire macro suite whenever a single helper function changes. By partitioning your macro logic into granular, library-internal modules, you significantly improve incremental compilation and maintainability.

The Strategy: Decoupling Logic from Implementation

To modularize effectively, separate your procedural macro crate into three distinct layers:

  1. The Entry Point (lib.rs): Strictly contains the #[proc_macro] attribute or derive declarations. Its only job is to receive the input TokenStream and delegate it to the logic crate.
  2. The Logic Crate (core): A standard Rust library crate that processes TokenStream types (using proc-macro2 for platform independence). Because this is a standard library, it can be unit-tested and incrementally compiled.
  3. The Back-end (gen): Modules responsible strictly for code emission or AST transformation.

Illustration

Instead of embedding complex logic inside your derive entry point, delegate to a structured backend:

// logic_module.rs
pub fn generate_impl(input: DeriveInput) -> TokenStream {
    let name = &input.ident;
    // Perform complex AST manipulation here
    quote! { impl #name { ... } }
}

// lib.rs
#[proc_macro_derive(MyTrait)]
pub fn my_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    logic_module::generate_impl(ast).into()
}

By isolating the generate_impl logic, the Rust compiler can cache the results of your logic crate independently of the proc-macro interface. If you change a small helper function in logic_module.rs, the compiler only needs to re-evaluate the transformation logic, rather than re-registering the entire macro entry point. Furthermore, using proc-macro2 allows you to write standard integration tests for your macro logic without the overhead of the proc-macro proc-macro crate, enabling faster iteration cycles and cleaner build artifacts.

Key Takeaway: Decouple your macro entry points from your transformation logic into separate, testable crates to ensure the compiler can cache your AST processing steps efficiently.