Error

"cannot find macro" — How to Fix

Fix the 'cannot find macro' error by adding the missing crate to Cargo.toml and using the #[macro_use] attribute or a use statement.

When the compiler can't find your macro

You copy a code snippet. You type my_macro!(args);. You hit compile. Rust responds with cannot find macro 'my_macro' in this scope. You check Cargo.toml. The dependency is there. You check the documentation. The macro exists. You stare at the screen. The macro is right there, yet Rust acts like it never heard of it.

This error happens because macros work differently than functions. The compiler needs to see the macro definition before it can expand the call. If the definition isn't in scope, the compiler has no instructions for generating the code, and it stops immediately.

Macros are recipes, not dishes

Functions are like pre-made dishes in a restaurant. You order cook_pasta(), and the kitchen serves the result. The kitchen knows how to make pasta because the recipe is built into the binary. The linker connects your call to the function code.

Macros are like stencils. When you write spray_pattern!(...), you are handing the compiler a stencil and asking it to paint a pattern onto your code. The compiler must have the stencil in hand at the exact moment it reads your call. It uses the stencil to generate new code, then compiles that new code. If the stencil isn't visible, the compiler can't generate anything.

The use statement is how you pick up the stencil. Without it, the compiler sees spray_pattern! but has no stencil to apply. It rejects the code before it even checks types.

The minimal fix

The fix is almost always a missing import. Add a use statement for the macro. The syntax looks just like importing a function or a type.

// main.rs

// Import the macro from the dependency crate.
// This brings the macro definition into scope.
use my_crate::my_macro;

fn main() {
    // Now the compiler can find the macro.
    // It expands the call before type checking.
    my_macro!(42);
}

If the macro lives in a module inside your own crate, import it the same way.

// lib.rs

// Re-export the macro so users can import it easily.
// This avoids forcing users to dig into internal modules.
pub use helpers::debug_log;

mod helpers {
    /// Prints a debug message with the file and line number.
    /// This macro captures the call site information.
    macro_rules! debug_log {
        ($msg:expr) => {
            println!("[DEBUG] {}:{} - {}", file!(), line!(), $msg);
        };
    }
}

Trust the use statement. It works for macros, functions, types, and constants. Rust 2018 unified the import system. You rarely need special syntax anymore.

Why macros need special imports

Functions and macros live in different namespaces in older Rust editions, but the scoping rules are the real story. Macro expansion happens during parsing, before the compiler resolves paths for functions. The compiler reads your source file top-to-bottom. When it hits a macro call, it immediately looks for the macro definition in the current scope.

If the macro is defined in another module, the compiler doesn't automatically search everywhere. It only looks where you told it to look. This keeps macro expansion predictable. If macros were globally visible by default, a macro named log! in one crate could accidentally shadow a log! macro in another crate, causing silent bugs.

The use statement explicitly brings the macro into scope. This makes dependencies clear. Anyone reading the code can see exactly where the macro comes from.

Convention aside: In Rust 2015, you had to use #[macro_use] extern crate my_crate; to pull macros into scope. That attribute imported every macro from the crate into the global namespace. It caused naming collisions and made it hard to track where macros came from. Rust 2018 removed the need for #[macro_use] in most cases. Use use statements. They are precise and modular.

Realistic example: Importing from a dependency

Here is a full example showing a dependency with a macro, the Cargo.toml configuration, and the correct import in main.rs.

// Cargo.toml
[dependencies]
// Add the crate that provides the macro.
// Ensure the version includes the macro you need.
my_tool = "1.2"
// src/main.rs

// Import the macro from the dependency.
// The path matches the crate name and the macro name.
use my_tool::config_macro;

// Import a regular function from the same crate.
// Macros and functions can coexist in the same import block.
use my_tool::run_tool;

fn main() {
    // Use the macro.
    // The compiler expands this before compiling the function.
    let settings = config_macro! {
        verbose: true,
        threads: 4
    };

    // Use the function.
    // This is a normal function call.
    run_tool(settings);
}

The macro config_macro! generates a struct initialization. The compiler replaces the macro call with the generated code. The use statement makes this possible. Without it, the compiler sees config_macro! and fails.

Pitfalls: Features, versions, and nightly

Sometimes the use statement is correct, but the error persists. Check these common causes.

Feature flags hide macros. Many crates gate optional macros behind feature flags. The macro exists in the crate, but it is compiled out unless you enable the feature.

# Cargo.toml
[dependencies]
my_tool = { version = "1.2", features = ["advanced_macros"] }

If the documentation shows the macro inside a section labeled "Available with feature advanced_macros", you must enable that feature. The compiler cannot find the macro because it was never compiled into the dependency.

Version mismatches. You might be using an old version of a crate. The macro was added in a newer release. Check the changelog. Update the version in Cargo.toml. Run cargo update.

Unstable macros require feature gates. Some macros are experimental. They only exist on the nightly compiler. The documentation marks them with a "nightly" badge.

// main.rs
// Enable the unstable feature.
// This line is required for nightly-only macros.
#![feature(trace_macros)]

use std::trace_macros;

fn main() {
    trace_macros!(true);
}

If you see cannot find macro for a macro that requires a feature gate, add #![feature(macro_name)] at the crate root. You must also be using the nightly compiler. Stable Rust does not allow feature-gated macros.

Procedural macros follow the same rules. Function-like procedural macros (like sql_query!(...)) behave like declarative macros. You import them with use. Derive macros are attributes, but the scoping rule is similar. The attribute name must be in scope. If you get an error about a missing derive, check that you imported the derive macro or that the crate re-exports it.

Build scripts need special handling. Build scripts (build.rs) run in a separate compilation context. use statements from the main crate do not work inside build.rs. If you need a macro in a build script, you often need #[macro_use] or to define the macro locally. This is one of the few places where #[macro_use] is still practical.

// build.rs
// Build scripts run separately.
// Use #[macro_use] to pull macros from a helper crate.
#[macro_use]
extern crate build_helper;

fn main() {
    // The macro is available here.
    generate_code!();
}

Macros expand before types check. If the compiler can't find the definition, it stops before it even looks at your logic. Fix the scope first.

Decision: How to import and troubleshoot

Use use path::to::macro_name; when importing a macro from a dependency or another module. This is the standard approach for Rust 2018 and later. It keeps imports explicit and avoids namespace pollution.

Use #![feature(macro_name)] when the macro is unstable and you are running the nightly compiler. Check the documentation for a "nightly" badge before adding this. You cannot use feature-gated macros on stable Rust.

Use feature flags in Cargo.toml when the macro is optional in the dependency crate. Look for features = [...] in the dependency's documentation. The macro is compiled out unless you enable the feature.

Check the crate version when the macro appears in the latest documentation but is missing in your code. The macro might have been added in a newer release. Update the version and run cargo update.

Avoid #[macro_use] in new code. It imports all macros from a crate into the global scope, which can cause naming collisions. Stick to use statements for precise control. Use #[macro_use] only in build scripts or when maintaining legacy code from Rust 2015.

Macros are recipes, not dishes. Bring the recipe to the compiler with a use statement.

Where to go next