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

I have a proc-macro that, among a bunch of other things, generates definitions for a number macro_rules! macros. Thing is, I'd like to be able to call that proc-macro any number of times in a given crate, including more than once.

Macro exports being what they are, this:

mod module {
    #[macro_export]
    macro_rules! mymacro {
        () => {
        }
    }

    pub use self::mymacro;
}

will work in terms of being able to call the macro as crate::module::mymacro!(...), but it will also make the macro available as crate::mymacro!(...). This latter part is the real issue: if the proc-macro is called again it will generate its own versions of those macro_rules macros, and those will clash with the previously defined versions.
This issue doesn't occur with the crate::module::mymacro!(...) versions because I have control over the module path there, and so can leverage that to prevent name collisions.

Is there a way to get rid of the crate::mymacro!(...) item entries without also nuking the crate::module::mymacro!(...) item entries?

If they don't have to be public outside the crate, I think that you can pub(crate) use mymacro.

If crate-public access (and thus #[macro_export]) is desired/required, you can work around the top-level name collisions by generating some (stable) hash mangle from the proc-macro input and including that as part of the macro's defined name, and then reëxporting the macro with the desired name into the module.

Long term (but shorter term than full "macros 2.0") there is vague intent to allow writing pub macro_rules! which would just have the desired item scoping. However, the experimental support (feature(pub_macro_rules)) was removed, due to ICEing cross-crate and nobody working to drive the feature. (Also the incomplete_features lint didn't exist yet.)

Without #[macro_export] slapped onto the macro_rules macro, that results in an error in pub use mymacro about not being able to export a private macro:

error[E0364]: `thing` is private, and cannot be re-exported
   --> gargle_snarfer/src/core.rs:143:9
    |
143 |         thing,
    |         ^^^^^

But it's also the #[macro_export] attribute that created the top-level crate::mymacro!(...) name in the first place AFAICT. So ideally I'd lop that off of the macro_rules macro definition, but then I need another way out of the error presented above.

In addition, ideally macro_rules macros would be not crate-visible at all, but only within the topmost module generated by the macro i.e. currently I'm using pub(in top_level_generated_module) use {all, the, macros} rather than pub(crate) use {all, the, macros}. That keeps every set of generated items out of each other's hair, and the top-level namespace unpolluted. Except for macro_rules macros that is, because of their special (and rather undesirable) resolution in the top-level crate module.

And finally, as an added bonus, those generated macro_rules macros should be exposed to the programmer as they provide the declarative mechanics to use the proc-macro. This part already works when using both #[macro_export] and pub use mymacro.

I just tested, and you can just pub(in path) use my_macro, so long as you never pub use without a path restriction.

mod b {
    crate::a::m!();
}

mod a {
    macro_rules! m {
        () => {};
    }
    pub(super) use m;
}

The fact that it's a restricted pub(in path) is important here, and I think potentially the only place where you have to use pub(in path) instead of just an unreachable pub.

2 Likes

(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:

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)
3 Likes

This did the trick. Thanks!

@Yandros That is also quite helpful in terms of refining my mental model of how declarative macros are resolved.

1 Like

This seems fine to me - doc(inline) is an explicit choice by the author, so I don't see any reason it shouldn't override hidden.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.