Lib.rs does not restrict the visibility scope for macro defined with macro_rules!

Hi everyone !

I want to move my app to library and I almost finished with all preparations. But I have an issue with my macro, defined with macro_rules!. I would like to restrict the scope for it, to make it visible only within my crate.

The macro is exported with #[macro_export] and when I try to check which imports are visible, importing all stuff in main.rs I still see this macro there.

How to make it not imported from lib.rs ?

I think the following should work:

mod macros {
    macro_rules! foobar {
        () => {}
    }
    // the trick to control macro visibility
    pub(crate) use foobar;
}

macros::foobar!();
2 Likes

on compile this returns an errors like:

error[E0433]: failed to resolve: could not find `with_opcode` in `macros`
  --> src/primary/client/auth/check_proof_code.rs:10:10
   |
10 |  macros::with_opcode! {
   |          ^^^^^^^^^^^ could not find `with_opcode` in `macros`

this is my macro:

mod macros {
    #[macro_export]
    macro_rules! with_opcode {
        (
            $(@login_opcode($login_opcode:expr))?
            $(@world_opcode($world_opcode:expr))?

            $(#[$outer:meta])*
            $vis:vis struct $PacketStruct:ident {
                $(
                    $(#[$field_attr: meta])? $field_vis:vis $field_name:ident: $field_type:ty
                ),*$(,)?
            }

            $($PacketStructImpl: item)*
        ) => {
            $(#[$outer])*
            $vis struct $PacketStruct {
                $($(#[$field_attr])? $field_vis $field_name: $field_type),*
            }

             $($PacketStructImpl)*

            impl $PacketStruct {
                $(
                    #[allow(dead_code)]
                    fn opcode() -> u8 {
                        $login_opcode as u8
                    }
                )?

                $(
                    #[allow(dead_code)]
                    fn opcode() -> u32 {
                        $world_opcode as u32
                    }
                )?
            }
        };
    }

    pub(crate) use with_opcode;
}

this is src/primary/macros/mod.rs:

mod with_opcode;

and this is one of the files where I use it:

use crate::primary::macros;
// ...
macros::with_opcode! {
    @login_opcode(Opcode::LOGIN_PROOF)
    #[derive(LoginPacket, Serialize, Deserialize, Debug)]
    struct Income {
        unknown: u8,
        code: u8,
    }
}
// ...

btw, RustRover also shows me an error:

Unresolved reference: `with_opcode`

Did I miss smth ?

What's the edition of the crate (in the Cargo.toml)?

edition = "2021"

Looks like you added a mod macros { that isn't wanted, and also in macros/mod.rs there is no pub(crate) re-export. If you want to avoid the macro_export global visibility, then you have to follow the nornal export rules, in which nothing is visible in a given module unless that module re-exports or defines it.

I am not sure, if I understand you correctly. Should I add smth to .../macros/mod.rs to re-export ?

Yes,

mod with_opcode;
pub(crate) use with_opcode::with_opcode;

This is exactly the same as you would do for a non-macro item in the same module layout.

1 Like

But I still need to wrap the macro in mod macros and use pub(crate) use with_opcode; inside ?
When for example I use next variant, code compiles:

// with_opcode.rs
pub mod macros {
    macro_rules! with_opcode {
        (
            $(@login_opcode($login_opcode:expr))?
            $(@world_opcode($world_opcode:expr))?

            $(#[$outer:meta])*
            $vis:vis struct $PacketStruct:ident {
                $(
                    $(#[$field_attr: meta])? $field_vis:vis $field_name:ident: $field_type:ty
                ),*$(,)?
            }

            $($PacketStructImpl: item)*
        ) => {
            $(#[$outer])*
            $vis struct $PacketStruct {
                $($(#[$field_attr])? $field_vis $field_name: $field_type),*
            }

             $($PacketStructImpl)*

            impl $PacketStruct {
                $(
                    #[allow(dead_code)]
                    fn opcode() -> u8 {
                        $login_opcode as u8
                    }
                )?

                $(
                    #[allow(dead_code)]
                    fn opcode() -> u32 {
                        $world_opcode as u32
                    }
                )?
            }
        };
    }

    pub(crate) use with_opcode;
}

// mod.rs
mod with_opcode;

pub use with_opcode::macros::with_opcode;

P.S. ah, there another error now:

error[E0364]: `with_opcode` is only public within the crate, and cannot be re-exported outside
 --> src/primary/macros/mod.rs:3:9
  |
3 | pub use with_opcode::macros::with_opcode;
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Solved: import should be replaced with:

pub(crate) use with_opcode::macros::with_opcode;

Final solution:

// with_opcode.rs
pub mod macros {
    macro_rules! with_opcode {
        (
            $(@login_opcode($login_opcode:expr))?
            $(@world_opcode($world_opcode:expr))?

            $(#[$outer:meta])*
            $vis:vis struct $PacketStruct:ident {
                $(
                    $(#[$field_attr: meta])? $field_vis:vis $field_name:ident: $field_type:ty
                ),*$(,)?
            }

            $($PacketStructImpl: item)*
        ) => {
            $(#[$outer])*
            $vis struct $PacketStruct {
                $($(#[$field_attr])? $field_vis $field_name: $field_type),*
            }

             $($PacketStructImpl)*

            impl $PacketStruct {
                $(
                    #[allow(dead_code)]
                    fn opcode() -> u8 {
                        $login_opcode as u8
                    }
                )?

                $(
                    #[allow(dead_code)]
                    fn opcode() -> u32 {
                        $world_opcode as u32
                    }
                )?
            }
        };
    }

    pub(crate) use with_opcode;
}

// mod.rs
mod with_opcode;

pub(crate) use with_opcode::macros::with_opcode;

Thanks to all for your time and your answers !

P.S. it is also possible to remove pub mod. So, according to @kpreid answer, this solution is even better:

// with_opcode.rs
macro_rules! with_opcode {
        (
            $(@login_opcode($login_opcode:expr))?
            $(@world_opcode($world_opcode:expr))?

            $(#[$outer:meta])*
            $vis:vis struct $PacketStruct:ident {
                $(
                    $(#[$field_attr: meta])? $field_vis:vis $field_name:ident: $field_type:ty
                ),*$(,)?
            }

            $($PacketStructImpl: item)*
        ) => {
            $(#[$outer])*
            $vis struct $PacketStruct {
                $($(#[$field_attr])? $field_vis $field_name: $field_type),*
            }

             $($PacketStructImpl)*

            impl $PacketStruct {
                $(
                    #[allow(dead_code)]
                    fn opcode() -> u8 {
                        $login_opcode as u8
                    }
                )?

                $(
                    #[allow(dead_code)]
                    fn opcode() -> u32 {
                        $world_opcode as u32
                    }
                )?
            }
        };
    }

pub(crate) use with_opcode;

// mod.rs
mod with_opcode;

pub(crate) use with_opcode::with_opcode;

No, you don't need an extra mod macros {} wrapper module. That was just part of @nerditation 's single-file example, analogous to your mod macros; that's using a file. The important part is the pub(crate) use with_opcode; which is placed wherever the macro is declared, but you can declare the macro in any module.

1 Like