Declarative and Procedural Macros in Depth

Exporting and Importing Macros

Rust provides two mechanisms for scoping and sharing declarative macro_rules! macros across modules and crates: modern path-based macro scoping (pub(crate) use macro_name; and pub use) and #[macro_export] coupled with the $crate metavariable for cross-crate exports.

Module-Scoped Macros via pub(crate) use / pub use

Historically, macro_rules! definitions followed textual source order and required #[macro_use] on mod declarations. In modern Rust (2018, 2021, and 2024 editions), you can re-export a macro_rules! macro right after its definition using pub(crate) use my_macro; or pub use my_macro; so it behaves like any normal module item—usable via module::my_macro!() or use module::my_macro; regardless of module declaration order:

mod logging {
    macro_rules! info_tagged {
        ($tag:expr, $($arg:tt)+) => {
            println!("[INFO:{}] {}", $tag, format_args!($($arg)+))
        };
    }

    // Export `info_tagged` as a path-addressable item of `mod logging`:
    pub(crate) use info_tagged;
}

fn main() {
    // Call via module path without `#[macro_use]`:
    logging::info_tagged!("db", "connected to {} shards", 4);
}
[INFO:db] connected to 4 shards

Exporting Macros to Downstream Crates with #[macro_export] and $crate

Annotating a macro_rules! definition with #[macro_export] places the macro in the root namespace of your crate (my_crate::my_macro!), regardless of which submodule defined it.

When an exported macro expands inside a downstream user's crate, any unqualified helper functions, structs, or inner macros it references would normally be looked up in the user's crate and fail to resolve. Prefixing every internal path with the $crate metavariable ($crate::module::helper_fn()) guarantees that paths resolve to your defining crate even if the user renamed your crate in Cargo.toml!

Why `$crate` Is Essential in `#[macro_export]`:

  Library Crate `metrics_lib`:
    #[macro_export]
    macro_rules! record {
        ($v:expr) => { $crate::internal::submit($v) };
                       ^^^^^^
    }                  Always resolves to `metrics_lib`, even inside downstream crates!
pub mod internal {
    #[doc(hidden)]
    pub fn format_kv(key: &str, val: &str) -> String {
        format!("{key}={val}")
    }
}

#[macro_export]
macro_rules! kv_pair {
    ($k:ident : $v:expr) => {
        // `$crate` resolves to the root of the crate that defined `kv_pair!`:
        $crate::internal::format_kv(stringify!($k), &$v.to_string())
    };
}

fn main() {
    let entry = kv_pair!(port: 8080);
    println!("entry: {}", entry);
}
entry: port=8080

Exporting Internal Helper Macros (#[macro_export] #[doc(hidden)])

If an exported macro #[macro_export] macro_rules! public_macro delegates internally to a helper macro, that helper macro must also be annotated with #[macro_export] (because expansion happens in the downstream caller's crate) and hidden from documentation using #[doc(hidden)] with a leading double-underscore name (__public_macro_internal!), invoked via $crate::__public_macro_internal!(...).