Sling Academy
Home/Rust/Applying #[macro_use] and #[macro_export] for Macro Crates in Rust

Applying #[macro_use] and #[macro_export] for Macro Crates in Rust

Last updated: January 04, 2025

Rust is a system programming language known for its safety, speed, and concurrency. One of the more powerful features in Rust is its macro system, which allows you to write code that writes other code, removing boilerplate and enabling greater flexibility. In this article, we'll explore how to use the #[macro_use] and #[macro_export] attributes to manage macro reusability and visibility in Rust, particularly when dealing with macro crates.

Understanding Macros in Rust

Macros in Rust are a way to write code patterns that can be expanded at compile time. They come in handy for tasks like code generation and manipulation. There are three kinds of macros in Rust:

  • Declarative macros: Also known as macros by example, this is the most common form. They use the macro_rules! macro.
  • Procedural macros: These are more flexible than declarative macros and are used for functions, derives, and attributes but can be more complex to write.
  • Attribute-like and function-like macros: These are part of procedural macros but require their own types of handling.

Macro Modules and Visibility

When macros are defined locally within a module, they are private by default. To make a macro available for use in other modules or crates, you need to increase its visibility. This is where the #[macro_export] attribute becomes relevant.

// In src/lib.rs
#[macro_export]
macro_rules! hello_world {
    () => {
        println!("Hello, world!");
    };
}

The #[macro_export] attribute makes the macro available in other modules or crates once your crate is included as a dependency.

Using #[macro_use] to Import Macros

When macros are exported using #[macro_export], any crate onboarding your library can use the #[macro_use] attribute to bring these macros into scope without having to explicitly import them.

// In another crate’s src/main.rs
#[macro_use]
extern crate your_macro_crate;

fn main() {
    hello_world!();
}

By specifying #[macro_use], all exported macros from the specified crate become usable in the importing crate. This attribute needs to be placed before the extern crate statement.

Best Practices

  • Use #[macro_export] when you intend to allow external crates to use your macros.
  • Use #[macro_use] sparingly in 2018 edition and later because it unconditionally brings all macros from the external crate into the scope, which might lead to name collisions.
  • For more controlled imports, prefer using the path-based imports with a module like use crate::macro_name; where possible.

Evolution and Current Recommendation

Starting with the 2018 Edition of Rust, macros can be imported more directly:

use your_macro_crate::hello_world;

This makes #[macro_use] less relevant, favoring specific imports to reduce the potential for macro name collisions and to keep your namespace cleaner.

Conclusion

Both #[macro_use] and #[macro_export] provide essential mechanisms in Rust for managing macro visibility and usability across crates. As the language evolves, recommendations may shift towards more explicit imports for clearer, more maintainable codebases. Understanding these practices will greatly enhance your ability to create robust libraries in Rust.

Next Article: Handling Repetitive Patterns with Submodules and Public APIs in Rust

Previous Article: Rust - Managing Version Conflicts Across Multiple Crates in a Workspace

Series: Packages, Crates, and Modules in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior