In this lesson, you will learn how to structure your procedural macros to minimize bottlenecking and maximize parallel compilation performance within large workspace dependency graphs.
The Bottleneck Problem
When you write complex procedural macros—such as those that perform intensive static analysis or deep metadata extraction—you often fall into the trap of writing "monolithic" macros. A monolithic macro acts as a serialization point for the Rust compiler (rustc). Because a single procedural macro must fully expand before the compiler can proceed with type checking, large codebases often see their compilation speed throttled by a single, expensive macro process that refuses to scale across CPU cores.
Granular Macro Splitting
To parallelize expansion, you must move away from the "one macro to rule them all" approach. Instead, decompose your macro logic into a hierarchy of smaller, discrete macros that operate on distinct, independent modules.
Consider a scenario where you generate boilerplate for a database schema. Instead of a single #[derive(Schema)] macro that traverses the entire AST of a large struct, split the logic:
// Instead of one giant macro, distribute the work:
#[derive(SchemaMetadata)]
struct User { ... }
#[derive(SchemaSerialization)]
struct User { ... }
#[derive(SchemaValidation)]
struct User { ... }
Strategic Crate Separation
By splitting the expansion logic into separate crates, you leverage the compiler's ability to run macro expansions in parallel across different crate units. If SchemaMetadata is defined in a utility crate, rustc can process the macro expansion for User in one thread, while simultaneously compiling unrelated crates in the workspace.
When your macros are granular, you reduce the "serial execution" time of the expansion phase. The compiler’s dep-info files can then more effectively cache the output of these smaller, independent expansion units. This prevents a change in one field of your struct from triggering a full re-expansion of every associated boilerplate, effectively "parallelizing" the workload across the dependency graph.
Key Takeaway: To parallelize macro expansion, decompose monolithic macros into smaller, specialized units that distribute the expansion workload across the dependency graph, allowing rustc to leverage multi-core concurrency effectively.