In this lesson, you will learn how to structure your workspace dependency graphs to ensure procedural macro crates remain decoupled from the crates that consume them, preventing circular dependencies and compilation bottlenecks.
The Challenge of Macro Dependencies
In a Rust monorepo, procedural macros occupy a unique position in the dependency graph. Because a procedural macro must be compiled before the code that uses it, you often encounter a "cycle" problem: if your macro crate (my-macro) depends on your business logic crate (my-logic), and my-logic uses my-macro, the compiler will stall.
To maintain a healthy workspace, you must enforce a strict unidirectional flow. Procedural macro crates should be "leaf" nodes or "foundation" nodes in your dependency tree—they should never depend on your domain-specific business logic.
Structural Isolation
To manage this, separate your functionality into three distinct layers:
- The API Crate (
my-core): Contains the data structures, traits, and types shared between the macro and the business logic. - The Macro Crate (
my-macro): Contains theproc-macro = truedeclaration. It depends onmy-corebut not on the business logic. - The Consumer Crate (
my-app): Depends on bothmy-coreandmy-macro.
By extracting shared definitions into a thin my-core crate, you break the circular dependency. Your my-macro crate only imports the lightweight types it needs to generate code, and my-app acts as the bridge that links them together.
# In my-macro/Cargo.toml
[dependencies]
my-core = { path = "../my-core" } # Safe: Macro uses core types
# In my-app/Cargo.toml
[dependencies]
my-core = { path = "../my-core" }
my-macro = { path = "../my-macro" }
Avoiding Re-compilation Spikes
When managing these graphs, be aware that any change to my-core triggers a re-compilation of both the macro crate and the consumer. To mitigate this, keep your shared dependency crate (my-core) as minimal as possible—ideally restricted to simple data structs or traits—so that the dependency graph remains shallow and predictable.
Key Takeaway: Always maintain a unidirectional dependency flow by extracting shared types into a dedicated core crate to prevent circular dependencies between your macros and domain logic.