How to Use Glob Imports (use module

:*) in Rust (and Why to Avoid Them)

Use explicit imports like `use module::item;` instead of `use module::*;` to prevent naming conflicts and improve code clarity.

The collision that breaks your build

You're halfway through refactoring a project. You add a new module, utils, and suddenly your build fails with a name collision error. You didn't touch the failing code. You just added use utils::*; at the top of your file. The compiler can't decide which Error type you mean, or which Result enum. You stare at the screen, wondering how adding a new module broke old code.

This is the classic glob import trap. The * syntax looks convenient. It promises to bring everything you need into scope without typing. In practice, it brings everything, including the things you don't need, and creates ambiguity that the compiler cannot resolve.

Packing the suitcase vs dumping the cabinet

Think of a use statement like a packing list for a trip. A specific import is packing your toothbrush. You know exactly what you're taking, and you can find it instantly when you need it.

A glob import is dumping the entire contents of your bathroom cabinet into your suitcase. You get everything you need, plus the expired medicine, the spare floss, and the bottle of shampoo that leaks. When you open the suitcase, you have to dig through the mess to find what you actually want. Worse, if you dump two cabinets in, you now have two bottles of shampoo and no idea which one is which.

Rust's compiler enforces a rule: every name in a scope must be unique. When you use use module::*;, you inject every public item from that module into your current scope. If two modules export an item with the same name, the compiler hits a wall. It cannot guess which one you intend.

The minimal collision

Here is the smallest example that triggers the problem.

/// Module A defines a Point for geometry.
mod geometry {
    /// A point in 2D space.
    pub struct Point {
        pub x: f64,
        pub y: f64,
    }
}

/// Module B defines a Point for UI rendering.
mod graphics {
    /// A point in screen coordinates.
    pub struct Point {
        pub screen_x: u32,
        pub screen_y: u32,
    }
}

// Brings geometry::Point into scope.
use geometry::*;
// Brings graphics::Point into scope.
use graphics::*;

fn main() {
    // E0252: the name `Point` is defined multiple times.
    // The compiler cannot resolve which Point you mean.
    let p = Point { x: 1.0, y: 2.0 };
}

The compiler rejects this with E0252 (the name Point is defined multiple times). The error points to the second glob import and tells you that Point is already defined. You have to choose: rename one of the imports, or stop using globs.

If you can't name the import, you don't understand the dependency.

What the compiler sees

When the compiler processes a file, it builds a symbol table. This table maps names to definitions. Explicit imports add one entry at a time. If you write use geometry::Point;, the compiler adds Point to the table. If you later write use graphics::Point;, the compiler sees a conflict and stops.

Glob imports add all public items from the module in one batch. The compiler processes the first glob, adds all its items, then processes the second glob. When it encounters a name that already exists, it raises E0252. The order of imports matters. The first glob wins the name. The second glob fails.

This behavior makes glob imports fragile. If you reorder your imports, the error might move or disappear, depending on which module provides the name you actually want. This creates a hidden dependency on import order. Your code works only because geometry is imported before graphics. Swap the lines, and the build breaks.

Globs are a debt instrument. Pay the interest in explicit names.

Shadowing: the silent killer

Name collisions are loud. The compiler yells at you. Shadowing is quiet. It lets you override an imported name without warning, which leads to subtle bugs.

/// Module with a utility function.
mod utils {
    /// Calculates the absolute distance.
    pub fn distance(a: f64, b: f64) -> f64 {
        (a - b).abs()
    }
}

// Imports distance into scope.
use utils::*;

/// Local function shadows the imported one.
fn distance(a: f64, b: f64) -> f64 {
    // Intentionally wrong for demonstration.
    a + b
}

fn main() {
    // Calls the local distance, not utils::distance.
    // The glob import made the shadowing silent.
    println!("{}", distance(5.0, 3.0)); // Prints 8.0, not 2.0.
}

The code compiles and runs. The local distance function shadows the imported one. The compiler does not warn you. If you intended to use utils::distance, you now have a bug that produces wrong results. With explicit imports, shadowing is still possible, but it's rarer because you have to type the name twice. Globs make shadowing easy by accident.

The compiler doesn't care about your typing speed. It cares about correctness.

Refactoring and the hidden dependency

Explicit imports act as a dependency checker. If you rename a function in a module, every file that imports it breaks. This is a feature. It forces you to update all callers. You get a list of errors that tells you exactly where the name is used.

Glob imports hide dependencies. If you rename a function in a module, code that uses the glob still compiles because the name is still in scope. You've broken the semantic link without the compiler telling you. The caller still calls the function, but the import no longer reflects the usage. This leads to zombie code where imports exist but aren't used, or callers that rely on names that shouldn't be public.

When you refactor a large codebase, explicit imports give you a map. You can search for use module::Function and find every file that depends on Function. With globs, you have to grep for the name in the code body, which misses cases where the name is used indirectly or shadowed.

Keep your namespace clean. Ambiguity is a bug waiting to happen.

Tooling and the noise floor

Modern editors use the Language Server Protocol to provide autocomplete, go-to-definition, and find-references. Glob imports add noise to the symbol table. Autocomplete becomes cluttered with items you don't need. Go-to-definition might jump to a list of candidates instead of the exact source. Find-references can miss usages if the tool has to resolve globs dynamically.

Explicit imports give the tooling a precise map of your code. The tooling works better when your imports are precise. You get faster autocomplete, accurate navigation, and reliable refactoring support. The Rust community convention is strict: avoid glob imports in library code. The linter clippy flags them with wildcard_imports. The warning exists because the community has collectively decided that explicit imports are superior for maintainability.

Trust the tooling. Precise imports make the IDE work for you.

The prelude exception

Rust itself uses a glob import mechanism under the hood. Every Rust file implicitly starts with use std::prelude::*;. This is why you can use Vec, String, and Option without importing them. The standard library curates this list carefully. It contains only the most essential types. This is the model for when globs are acceptable: a tightly controlled, documented set of items that are used everywhere.

Library authors sometimes use pub use module::*; to create a prelude module. This is a deliberate design choice, not laziness. The author controls all the modules and ensures no collisions. The glob is a tool for API design, not for lazy importing. Even here, document the prelude so users know what they are getting.

Reserve globs for prelude modules. Everywhere else, name your imports.

When to use globs vs explicit imports

Use explicit imports (use module::Item;) for production code to keep dependencies clear and prevent collisions.

Reach for glob imports (use module::*;) only in test files where brevity outweighs precision and the test scope is isolated.

Pick fully qualified paths (module::Item) when names are common like Error or Result and you want to avoid ambiguity entirely.

Reserve pub use globs for prelude modules when you are designing a crate's public API and want to offer a convenient import bundle for users.

Explicit imports are documentation. They tell the reader exactly what this file needs.

Where to go next