Rust Declarative Macros: Organization, Scoping, and Common Use-Cases
This article, the second in a series, delves into how to incorporate declarative macros into Rust crates. It covers macro scoping, organization, exporting, and provides examples of common use-cases. The article assumes familiarity with the basics of declarative macros and focuses on more advanced techniques.
Macro scoping in Rust can be understood in two ways: textual scoping and path-based scoping. Textual scoping is the default and is based on where the macro is defined within a file. Path-based scoping, on the other hand, treats the macro as an item in the module tree, similar to standard Rust items. While path-based scoping is generally preferred, understanding textual scoping is still valuable as it's widely used.
One key difference between textual and path-based scoping is that macros with textual scoping must be used within the same module, function, or logical scope where they're defined. They can be used by submodules without explicit import. In contrast, path-based scoped macros can be used before their definition, as they're effectively items in the module tree. This flexibility can make path-based scoped macros more convenient and powerful in certain situations.
Visibility and access rules for textual scoped macros are counter-intuitive. They're only available after their definition within a module, function, or logical scope. However, if defined within the module itself, they're available throughout the module and its submodules. This can be useful for functions with repetitive internal logic, as it keeps the macro close to its usage.
There are scenarios where you might want a macro to be accessible to modules defined in the parent module. Historically, this was achieved using the #[macro_use] attribute on the containing module. However, this approach has limitations, such as not propagating up the module tree automatically. To address this, you can manually import or resolve the macro after its definition, treating it like any other item. This allows the macro to be used in any module that imports it.
Exporting a macro makes it accessible to users of your crate. However, this should be done judiciously, as exported macros can become dependencies for users, complicating changes and maintenance. To export a macro, use the #[macro_export] attribute. When defining a macro for external use, prefer path-based scoping and manually import or resolve it in the desired modules. This approach ensures the macro's usage is clear and flexible.
Written by urgent.news from Lobsters's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.