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 asmacro_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
-lessmacro_rules!
definitions;
-
That is, the expected workflow I could imagine is:
-
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. -
they reälize they want the macro to be called from a parent module (or downstream crate). They then slap
pub(crate)
(orpub
) 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::…
(this is the only difference with the "perfect" (but stuck) proposal of having
pub
-lessmacro_rules!
behave exactly likepub(self) macro_rules!
: theuse super::*;
would have been already needed to begin with).
- but for the submodules now needing a not-so-surprising
-
That seems like a rather seamless workflow, despite the legacy behavior for pub
-less macro_rules!
.
FWIW, in the pub
lic 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). 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;
)*
})}