The re-export pattern
So far, the code that uses a derive macro has depended directly on the proc-macro crate. This works, but there’s a problem: proc-macro crates are limited in what they can export.
A proc-macro = true crate can only export procedural macros. It cannot export structs,
traits, functions, or any other items. If your macro generates code that references a trait
or helper type, that trait must live somewhere else.
The problem
Say you’re building a Describe derive macro that implements a Describe trait:
#![allow(unused)]
fn main() {
pub trait Describe {
fn describe(&self) -> &'static str;
}
}
The trait definition can’t go in the proc-macro crate. So where does it go?
The facade pattern
The standard approach is a facade crate that re-exports the macro alongside any traits, types, or functions that the macro’s generated code needs:
my-lib/ ← facade crate (what users depend on)
├── Cargo.toml
├── src/lib.rs ← defines the trait, re-exports the macro
│
└── my-lib-macros/ ← proc-macro crate (implementation detail)
├── Cargo.toml
└── src/lib.rs ← #[proc_macro_derive(Describe)]
The facade crate (my-lib) depends on the proc-macro crate and re-exports the macro:
#![allow(unused)]
fn main() {
// my-lib/src/lib.rs
pub use my_lib_macros::Describe;
pub trait Describe {
fn describe(&self) -> &'static str;
}
}
Users only depend on my-lib:
[dependencies]
my-lib = "1.0"
And they get both the trait and the macro from a single import:
#![allow(unused)]
fn main() {
use my_lib::Describe;
#[derive(Describe)]
struct Point { x: f64, y: f64 }
}
Real-world examples
This pattern is everywhere:
serdere-exports macros fromserde_derive.thiserrorre-exports macros fromthiserror-impl.clapre-exports macros fromclap_derive.tokiore-exports macros fromtokio-macros.
If you’ve ever used #[derive(Serialize)] from serde without adding serde_derive to your
dependencies, it’s because serde re-exports it for you.
Exercise
The exercise for this section is located in 03_basics/08_re_export_pattern
The re-export-exercise-macros/ subdirectory already contains a working Describe derive macro
that implements a SelfDescribe trait. Your job is to wire up the facade pattern:
- Add the
re-export-exercise-macroscrate as a path dependency inCargo.toml. - Re-export the
Describederive macro withpub useinsrc/lib.rs, so that downstream code can import both the trait and the macro from a single crate.