Two macro scoping questions

Don't worry, they're the easy kind, nothing to do with hygiene...

  1. The standard library has a handful of macros you can't import from the crate root. The ones I know are std::pin::pin! and std::mem::offset_of!. User crates can't do that in stable Rust, right?

  2. It seems like #[macro_use] and #[macro_export] and the above restriction could all be forgotten if Rust allowed pub on macro_rules! macros. (I'm imagining it would cause the macro to have path-based scope.) Is there some reason that wouldn't work?

I just remembered this hack (playground):

pub mod macros {
    #[macro_export] macro_rules! potato { () => {()} }

    pub use potato;
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_potato() {
        assert_eq!(crate::macros::potato!(), ());
    }
}

It still doesn't quite do what std::pin::pin! does, though, right? potato is still visible at the crate root.

that's correct, the standard library uses what's called declarative macros 2.0, which is unstable. tracking issue is #39412

the new macros 2.0 system is design to address this problem, it uses a new keyword macro instead of changing the behavior of old macro_rules for backward compatibility.

as for why the current macro system works this way, I believe it's all historical: macros were originally (pre-1.0) not intended to be used from external crates, and had textual scope only. I was not using rust back then, but I vaguely remember having read about it somewhere. it might be possible to use macros inter-crates, but it was not easy. the macro_export and macro_use attributes were a after thought and was added (I believe) in the 1.0 release to make inter-crate macro usage easier.

see also the scoping section of the reference.

You can't avoid it being accessible at the crate root, but you can obfuscate it:

pub mod macros {
    #[doc(hidden)]
    #[macro_export]
    macro_rules! _macros_potato { () => {()} }

    /// documentation for the macro goes here
    pub use _macros_potato as potato;
}

This way, the unwanted root export will not appear in the documentation, and to use it they'd have to write your_crate::_macros_potato!(), which no one is going to write by accident.

Also, if it were the case that you intend the macro to be used only within the same crate, then you could skip #[macro_export] entirely as long as you restrict the visibility of the use to the crate:

mod macros {
    macro_rules! potato { () => {()} }
    pub(crate) use potato;
}

This macro is truly only reachable through the intended path.

1 Like

Sure, but my point is that "macros 2.0" has been stalled for the better part of 8 years now, and supporting pub would not change the behavior of old macro_rules, so it would not affect backward compatibility.

@kpreid If you don't mind, I'd like to include this information in the next edition of Programming Rust, using different text but pretty similar example code. (There are not a lot of ways to paraphrase the code. Almost every element is worth keeping.)

Please, feel free to use it however you see fit.

Additionally, as you note, it would be hard to write the code any differently other than the choice of module name β€” and I did not invent this technique from scratch, either. Thus there is nothing original and copyrightable and you do not need my permission. But, for what it’s worth, you have it.

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.