Skip to content

← Modules, Visibility, Testing and Docs step 6 of 22

Medium Primitives

Glob imports, shadowing, and ambiguity

use foo::*; drags every public name out of foo into your module. It is the fastest way to make a Rust codebase unreadable, and it is also, occasionally, exactly right. What makes it worth a whole item is that the shadowing rules are not obvious, and experienced people guess.

pub fn resolve(bindings: Vec<(String, String, String)>, name: &str) -> String

Each binding is (kind, name, target_path) with kind one of "item", "use", "glob", "prelude". Return the path name resolves to, or "ambiguous", or "unresolved".

The precedence ladder

item  ≻  explicit use  ≻  glob  ≻  prelude

An item you declared beats a name you imported. An explicit use beats a glob. A glob beats the prelude — which is why use my_prelude::*; can shadow Result and nobody notices for a while.

Two things follow that are easy to get wrong:

  • The first tier with anything to say wins outright. If an explicit use binds the name, two conflicting globs below it are simply never consulted, and there is no error. Ambiguity is decided within a tier.
  • Two globs that pull in the same path are not a conflict. There is still only one thing the name could mean. Deduplicate by target before you count.

Two globs pulling in different items with the same name is E0659 — X is ambiguous, and the timing of that error is the interesting part: it does not fire when you write the globs. It fires the first time somebody uses the name. So the error appears in a file nobody edited, blaming a line that has been there for months, because a dependency you upgraded added one public item.

pub use x::*; twice is worse

Re-exporting two globs with an overlapping name produces the warn-by-default ambiguous_glob_reexports and silently drops one of them from your public API. Not an error, not a resolution failure — one of the two names is simply not there for your users. This is the strongest single argument against glob re-exports in a library.

The community genuinely disagrees

clippy::wildcard_imports (pedantic, off by default) wants globs gone. Its carve-outs tell you where the argument sits: it deliberately exempts paths containing prelude, and use super::*; inside a module whose name contains “test”. Those two are considered idiomatic by nearly everyone. clippy::enum_glob_use is narrower and targets use Ordering::*;, which reads beautifully inside a match and destroys your ability to grep for Less.

There is no settled answer. What is settled is that you should be able to predict what a glob does before you write one, which is what this problem is for.

Contract

  • Unknown name with no bindings at all: "unresolved".
  • More than one distinct target in the winning tier: "ambiguous".
  • Duplicate identical bindings in a tier collapse to one.