Question

Using macro_rules! Across Module Files in Rust

rustmodulerust-macros

Question

I have two modules in separate files within the same Rust crate. The crate uses macro_rules!, and I want to use a macro defined in one module file from another module file.

For example:

// macros.rs
macro_rules! my_macro {
    () => {
        println!("Hello from macro");
    };
}
// something.rs
use macros;
// use macros::my_macro; // this does not work for macro_rules!

fn demo() {
    my_macro!();
}

Right now I get a compiler error like cannot find macro 'my_macro' in this scope or macro undefined: 'my_macro'.

Since macros are handled differently from normal items such as functions or structs, how can I make a macro_rules! macro defined in one module available in another module file within the same crate?

Short Answer

By the end of this page, you will understand how macro_rules! visibility works in Rust, why macros behave differently from normal imports, and the main ways to share a macro between module files in the same crate. You will also see practical patterns, common mistakes, and the modern preferred approach.

Concept

macro_rules! macros in Rust are not exactly like functions, structs, or modules. A normal item is usually accessed through the module system with paths such as crate::utils::helper(). Macros have their own scoping and visibility rules.

The key idea is:

  • Functions and structs are resolved through the normal module namespace.
  • macro_rules! macros historically use a separate macro namespace and have special import/export behavior.

This matters because code like this does not behave the same way as a function import:

use macros::my_macro;

For older-style macro_rules! macros, that often does not work the way beginners expect.

Why this matters

In real Rust projects, macros are often used to:

  • reduce repetitive code
  • build custom logging or assertions
  • generate implementations
  • create small domain-specific syntax

If you split your crate into multiple files, you need to know how macro visibility works, or the compiler will say the macro is not in scope.

The main approaches

There are two common patterns when working with macro_rules! across files:

  1. Bring macros into scope with #[macro_use]

    • Common in older Rust code
    • Often used when a mod macros; file contains reusable macros
  2. Export the macro and call it with a path

    • Uses #[macro_export]
    • Makes the macro available from the crate root
    • More explicit and often easier to follow in modern Rust

A very important detail: #[macro_export] exports the macro to the crate root, not as a normal module item inside macros::....

So even if the macro is written in macros.rs, you typically call it like this:

crate::my_macro!();

not this:

crate::macros::my_macro!();

unless you are using newer macro path behavior in a specific setup.

Why the compiler error happens

The error happens because defining a macro in macros.rs does not automatically make it visible everywhere else. The macro must be:

  • defined before use in the right scope, or
  • imported into scope, or
  • exported and called through the proper path

That is the core concept behind the problem.

Mental Model

Think of Rust macros as special tools stored in a toolbox that is separate from your normal code shelves.

  • Your module system is like shelves labeled utils, models, and api.
  • Your macros are like special tools that do not automatically sit on those shelves in the same way.

So if you put a hammer in a room called macros.rs, that does not mean another room can automatically grab it with a normal shelf lookup.

You need to either:

  • place the tool into a shared toolbox everyone can use (#[macro_use]), or
  • register it so it can be found from the main entrance (#[macro_export] and crate::my_macro!())

That is why macros feel different from functions: they follow special access rules.

Take Quiz

Syntax and Examples

Basic pattern with #[macro_use]

This is a traditional way to make macros from one module available to the rest of the crate.

// main.rs or lib.rs
#[macro_use]
mod macros;
mod something;
// macros.rs
macro_rules! my_macro {
    () => {
        println!("Hello from macro");
    };
}
// something.rs
pub fn demo() {
    my_macro!();
}

Explanation

  • #[macro_use] mod macros; tells Rust to load macros from that module into scope.
  • After that, sibling modules such as something.rs can use my_macro!().

Pattern with #[macro_export]

Another common approach is to export the macro to the crate root.

// lib.rs or main.rs
mod macros;
mod something;
// macros.rs
#[macro_export]
macro_rules! my_macro {
    () => {
        println!("Hello from exported macro");
    };
}
// something.rs
pub fn demo() {
    crate::my_macro!();
}

Explanation

  • #[macro_export] makes the macro available at the crate root.
  • You call it with crate::my_macro!() from inside the same crate.
  • You do not import it like a normal function from macros::my_macro.

A complete small example

// lib.rs
#[macro_use]
mod macros;
mod something;

pub fn run() {
    something::demo();
}
// macros.rs
macro_rules! my_macro {
    () => {
        println!("It works");
    };
}
// something.rs
pub fn demo() {
    my_macro!();
}

When run() is called, something::demo() can use my_macro!() because the macro was brought into scope from the macros module.

Step by Step Execution

Consider this example:

// lib.rs
#[macro_use]
mod macros;
mod something;
// macros.rs
macro_rules! my_macro {
    () => {
        println!("Hello");
    };
}
// something.rs
pub fn demo() {
    my_macro!();
}

What happens step by step

  1. Rust starts reading lib.rs.
  2. It sees #[macro_use] mod macros;.
  3. The macros module is loaded, and its macro_rules! macros are made available.
  4. Rust then processes mod something;.
  5. Inside something.rs, the call my_macro!() is now recognized.
  6. The macro expands into:
println!("Hello");
  1. The resulting code is compiled like normal Rust code.

Trace of the expansion

Original:

my_macro!();

After expansion:

println!("Hello");

That expansion step is why the macro must be visible at the time Rust resolves it.

Real World Use Cases

Macros shared across module files are useful when a project has repeated patterns.

Common real uses

  • Custom logging macros

    • Example: debug_db!() to print SQL queries only in development.
  • Assertion helpers for tests

    • Example: assert_contains!() used in multiple test modules.
  • Boilerplate reduction

    • Example: generating repeated implementations for multiple types.
  • Consistent error creation

    • Example: a macro that creates formatted errors with file and line information.
  • Domain-specific shortcuts

    • Example: building route definitions, SQL fragments, or event declarations.

Example scenario

A crate might contain:

  • macros.rs for reusable macros
  • parser.rs that uses a macro to reduce repetitive parsing code
  • tests.rs that uses custom assertion macros

This keeps repeated logic in one place while allowing many modules to use it.

Take Quiz

Real Codebase Usage

In real Rust codebases, developers usually organize macros in a dedicated module such as macros.rs and make them available in one of a few clear ways.

Common patterns

1. Central macro module

#[macro_use]
mod macros;

This is common in older codebases and internal crates where macros are used widely.

2. Export to crate root

#[macro_export]
macro_rules! my_macro {
    () => { /* ... */ };
}

Then use:

crate::my_macro!();

This is explicit and works well when the macro should be accessible broadly.

3. Helper macros for validation or early returns

Macros are often used to simplify repetitive checks such as:

macro_rules! ensure {
    ($cond:expr, $msg:expr) => {
        if !$cond {
            return Err($msg.into());
        }
    };
}

This can reduce repeated validation code in many modules.

4. Test support macros

Projects often place assertion helpers in a shared macro file so multiple test modules can use them.

Practical advice

  • Keep macro names clear and specific.
  • Avoid overusing macros when a function is enough.
  • Prefer explicit usage so readers can tell where a macro comes from.
  • Put shared macros near the crate root if many modules depend on them.

Common Mistakes

1. Trying to import a macro_rules! macro like a normal item

Broken example:

use macros::my_macro;

Why it fails:

  • macro_rules! macros are not always imported like functions or structs.
  • Older macro behavior especially differs from normal item imports.

How to fix it:

  • Use #[macro_use] mod macros;, or
  • Use #[macro_export] and call crate::my_macro!()

2. Expecting #[macro_export] to keep the macro under the module path

Broken assumption:

crate::macros::my_macro!();

Why it fails:

  • #[macro_export] exports the macro at the crate root.

Correct usage:

crate::my_macro!();

3. Declaring modules in the wrong order

Broken example:

mod something;
#[macro_use]
mod macros;

This can cause scope problems in some setups because something may be processed before the macro is available.

Better:

#[macro_use]
mod macros;
mod something;

4. Using a macro when a function would be simpler

Sometimes beginners write a macro for logic that should just be a function.

Avoid this if simple code is enough:

macro_rules! add_one {
    ($x:expr) => {
        $x + 1
    };
}

Prefer a function when possible:

fn add_one(x: i32) -> i32 {
    x + 1
}

Macros are most useful when you need syntax transformation or code generation.


5. Forgetting that macros expand into code

A macro must expand into valid Rust for the place where it is used.

Broken example:

macro_rules! bad_macro {
    () => {
        let x = 5;
    };
}

fn main() {
    let y = bad_macro!();
}

This fails because the expansion does not produce an expression suitable for assignment.

Always think about what the expanded code becomes.

Comparisons

#[macro_use] vs #[macro_export]

ApproachWhat it doesBest forTrade-off
#[macro_use] mod macros;Brings macros from a module into scopeInternal crate-wide macro sharingLess explicit
#[macro_export]Exports macro to crate rootMacros used broadly or externallyPath can be surprising

Macros vs functions

Featuremacro_rules! macroFunction
Resolved through normal module importsNot alwaysYes
Works with variable syntax patternsYesNo
Type checked before expansionNoYes
Good for simple reusable logicSometimesUsually better
Can generate codeYesNo

Calling styles

StyleExampleNotes
Unqualified macro callmy_macro!()Works when macro is in scope
Crate-root pathcrate::my_macro!()Common with #[macro_export]
Normal item path importuse macros::my_macro;Not the typical old-style macro_rules! approach
Take Quiz

Cheat Sheet

Quick reference

Define a macro

macro_rules! my_macro {
    () => {
        println!("Hello");
    };
}

Make macros from macros.rs available in the crate

#[macro_use]
mod macros;

Export a macro to crate root

#[macro_export]
macro_rules! my_macro {
    () => {
        println!("Hello");
    };
}

Use it from the same crate:

crate::my_macro!();

Important rules

  • macro_rules! macros are not always used like normal imported items.
  • #[macro_export] places the macro at the crate root.
  • Module declaration order can matter.
  • If many files use a macro, put it in macros.rs and load it early.
  • Prefer functions unless you need macro behavior.

Typical setup

// lib.rs or main.rs
#[macro_use]
mod macros;
mod something;

Common fix for “cannot find macro in this scope”

  • Ensure the macro module is declared before the module that uses it.
  • Use #[macro_use] or #[macro_export] correctly.
  • If exported, call it as crate::my_macro!().

Edge case to remember

This usually does not mean what beginners expect:

use macros::my_macro;

FAQ

Why can I not import a macro_rules! macro like a normal function?

Because macros use special scoping and resolution rules. They are not ordinary module items in the same way functions are.

What does #[macro_use] do in Rust?

It brings macros from a module into scope so they can be used elsewhere in the crate without normal item imports.

What does #[macro_export] do?

It exports the macro to the crate root, which means you can usually call it as crate::my_macro!() inside the same crate.

Should I use #[macro_use] or #[macro_export]?

For internal crate organization, #[macro_use] is a common older pattern. For explicit crate-root access, #[macro_export] is often clearer.

Why does crate::macros::my_macro!() not work?

Because #[macro_export] exports the macro at the crate root, not under the original module path.

Does module order matter for macros?

Yes. If the macro is not made available before another module tries to use it, you can get scope errors.

Should I write a macro or a function?

Use a function unless you need syntax-level code generation, flexible token matching, or macro-specific behavior.

Take Quiz

Mini Project

Description

Create a small Rust crate with a shared macro file and another module that uses the macro. This demonstrates how to organize macro_rules! macros across multiple files in a practical project structure.

Goal

Build a crate where a macro defined in macros.rs is successfully used inside something.rs.

Requirements

Create a macros.rs file that defines one reusable macro_rules! macro. Declare the macro module from lib.rs or main.rs so the macro is available. Create a second module file that calls the macro. Run the project and confirm the macro expands and prints output.

Take Quiz

Keep learning

Related questions

View more