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

Managing Macro Crates

You will structure multi-crate workspaces to decouple procedural macro logic from end-user implementation.

In this lesson, you will learn how to structure your Rust workspaces to decouple procedural macro logic from your runtime crate, ensuring clean compilation boundaries and stable dependency management.

The Dependency Restriction

Rust enforces a strict rule for procedural macros: they must reside in a crate with the proc-macro = true flag in their Cargo.toml. Crucially, these crates cannot export any other public items (like structs or traits) that aren't macros. If you try to mix a runtime library with your macro logic in the same crate, your users will be forced to depend on the macro crate, which often leads to circular dependencies or cryptic compilation errors.

The Workspace Strategy

To maintain a professional codebase, you should adopt a "Macro/Runtime" split within a single workspace. This allows you to isolate the complexity of syn and quote from the API your end-users consume.

  1. my-crate-macros: The procedural macro crate. It contains the proc_macro implementation and depends on syn, quote, and proc-macro2.
  2. my-crate: The main runtime crate. It depends on my-crate-macros.

In my-crate/src/lib.rs, you re-export the macros so users don’t have to depend on your internal macro crate directly:

// In my-crate/src/lib.rs
pub use my_crate_macros::MyMacro;

// Your actual runtime code
pub fn example_runtime_fn() { ... }

Why this decoupling works

By keeping the macro logic separate, you avoid "dependency pollution." Since procedural macro crates must be compiled for the host architecture (the compiler) rather than the target architecture, isolating them ensures that dependencies like syn—which are heavy—don't bloat the build graph of your runtime crate. Furthermore, this structure allows you to iterate on your macro logic without triggering unnecessary recompilations of the core runtime logic, keeping your development feedback loop tight.

When managing this architecture, always treat your macro crate as a private implementation detail. If you find yourself needing to share helper types between the macro and the runtime, define them in a third, minimal my-crate-core crate that both can depend on.

Key Takeaway: Always split procedural macros into a dedicated crate with proc-macro = true and re-export them from your main crate to maintain clean dependency boundaries and compile-time efficiency.