`pub` on `macro_rules!`

Regarding the proposal of $pub:vis macro_rules!, it's still stuck on the meaning of /* no vis */ macro_rules! (henceforth referred to as "pub-less macro_rules!"):

  • the idea was for it to mean the same as pub(self) macro_rules!, but this breaks certain old recursive macro definitions which just assume to be in scope even in parent modules (through #[macro_use]) or whatnot;

    • obviously the "breaking change" here was imagined in an edition boundary, but on the condition that there be nice compiler-generated suggestion to fix it. And that turned out to be a rabbit hole of edge cases that blocked the whole thing.

One thing we could do, thus, is to punt on pub-less macro_rules! meaning the same as pub(self) macro_rules!, and still enable the non-pub-less macro_rules! (pub macro_rules!, pub(crate) macro_rules!, pub(self) macro_rules!, etc.):

  • the compiler machinery for all this is already available, it's just a matter of switching the knob to enable it;

  • as the rest of my post will show, the world of macros is already plagued with hacks. So having pub(self) macro_rules! not be exactly the same as macro_rules! will:

    • be hardly noticed except for people writing pub(self) macro_rules! … at the end of a module (at which point they'll probably be doing so deliberately);

    • still be order of magnitudes better than the current plethora of hacks;

    • remain in line with the ideä of an on-edition-boundary change for pub-less macro_rules! definitions;

That is, the expected workflow I could imagine is:

  1. Somebody writes macro_rules! macro_name … at the beginning of a module, as they do now. The macro works for the current module, and submodules defined after the macro.

  2. they reälize they want the macro to be called from a parent module (or downstream crate). They then slap pub(crate) (or pub) on it:

    • the parent module (or downstream crate) can now refer to it by path;

    • current stuff Just Works™

      • but for the submodules now needing a not-so-surprising use super::… (:point_left: this is the only difference with the "perfect" (but stuck) proposal of having pub-less macro_rules! behave exactly like pub(self) macro_rules!: the use super::*; would have been already needed to begin with).

That seems like a rather seamless workflow, despite the legacy behavior for pub-less macro_rules!.


FWIW, in the public case, now that rustdoc lets #[doc(inline)] override a #[doc(hidden)], the current approach is to do:

#[doc(hidden)] #[macro_export]
macro_rules! __foo { … }

/// …
#[doc(inline)]
pub use __foo as foo;

This yields a properly-scoped and pub-visible macro, like a pub macro_rules! would (or pub macro does).

  • the only caveat is that this still requires you to avoid using the same foo name multiple times per crate (since the #[macro_export] will make them clash at the root of the crate :pensive:). If this is something you need, then using one of the more feature rich aforementioned helper crates may be warranted.

The non-pub case is then the

macro_rules! __foo { … }
pub(restriction) use __foo as foo;

you mentioned (and we almost always can use foo instead of __foo, thereby simplifying it down to your:

I'd say that beyond immediately-called macros,

(+ #[macro_use]) ought to be avoided.


All this does mean that it is possible to define your own meta-macro to handle this properly, much like the aforementioned third-party libs, except now we can reduce it down to the pervasive paste!:

scoped! {
    pub macro_rules! foo { … }
    // and/or
    pub(crate) macro_rules! bar { … }
    // and/or
    macro_rules! baz { … }
}
#[macro_export]
macro_rules! scoped {(
    $(
        $( #$attr:tt )*
        $( pub ($($restricted:tt)+) )?
        $( pub $(@$if_pub:tt)?      )?
        //     ^^^^^^^^^^^^^^^
        //     `$if_pub:empty` matcher when? 🥺👉👈
        macro_rules! $macro:ident $rules:tt
    )*
) => (::paste::paste! {
    $(
        $( #$attr )*
        $($($if_pub)? #[doc(hidden)] #[macro_export] )?
        macro_rules! [< __ $macro >] $rules

        $( #$attr )*
        #[doc(hidden)]
        #[allow(unused)]
        $(pub ($($restricted)+))? $($($if_pub)? pub)?
        use [< __ $macro >] as $macro;
    )*
})}
12 Likes