How to namespace a macro_rules macro within a module or #[macro_export] it without polluting the top-level namespace

(Meta: I've taken the liberty to reword a bit the thread's title to better take into account what the thread is about).

So, regarding the problem of namespacing a macro within a module, then indeed any non-pub re-export written after the macro definition will achieve the desired effect, as I explained in this SO answer:

https://stackoverflow.com/a/67140319/10776437

That is, while you can't write:

//! Doesn't work!
mod a {
    pub(…) macro_rules! name { … }
}

you can write:

mod a {
    macro_rules! name { … }
    pub(…) use name;
}

to achieve the same effect.

  • To be extra clear: this won't work with pub privacy, unless the macro is #[macro_export]-ed (that is, without that annotation, the maximum visibility of macro_rules! macros is pub(crate)).

  • Extra stuff to do if name collides with prelude attribute macros (e.g., forbid, deny, warn, allow, test)

    You must use a different name for the macro_rules! definition and then amend the name with the re-export:

    macro_rules! __warn__ { … }
    pub(…) use __warn__ as warn;
    

At that point, you have escaped the macro idiosyncrasies, and then you are back to a good old namespacing / re-exporting problem.


Answering the original thread's title / XY problem: quid of #[macro_export]ed macros?

Sometimes we may need this behavior (that of namespacing a macro within a module) but for a fully pub macro, since we just want to namespace a public / downstream-user-facing macro_rules! macro.

In that case, for a macro_rules! macro, annotating the macro with #[macro_export] is necessary, and this will kind of magically perform a pub use path::to::that_macro; at the root of the crate. So this, indeed, pollutes the top-level module of the crate with that macro.

There are two ways to palliate the issue:

  • The most robust but also cumbersome way is to involve an extra helper crate to perform the #[macro_export]s of those macros for use, and then we can pub use ::that_helper_crate::macro_name; within the module of our choosing. It does involve an extra helper crate, and breaks $crate, basically featuring the main drawbacks of procedural macros.

  • Another approach is to #[doc(hidden)]-tag the macro, and mangle a bit the name, so that in practice it doesn't really pollute the top-level module:

    #[doc(hidden)]
    #[macro_export]
    macro_rules! __macro_name__ { … }
    
    #[doc(inline)]
    pub use __macro_name__ as macro_name;
    

    Sadly, #[doc(inline)] is currently unable to override the original #[doc(hidden)], which makes this technique unusable in practice (I've already mentioned this issue to @jyn514 and @GuillaumeGomez: the fix is trivial to write, but whether #[doc(inline)] ought to be able to override a #[doc(hidden)] is a more debatable thing hence why I'm waiting on their green light).

    • At this point the only thing that remains is to hack a DIY #[doc(hidden)], through js / css hacks :grimacing: (real life example: hack, result)
4 Likes