RFA-599 · Case file with fixtures · Case 571 of 694 · Compiler evidence
Rust Glob Re-exports Can Create an Ambiguous Public Name
Glob imports hide which names enter a namespace and can collide as modules evolve. Preserve qualified domain paths or re-export an intentional façade explicitly.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- Glob re-exports imported future and existing names without preserving their source namespaces, creating two candidates under one public identity.
- First discriminating check
- Trace every candidate import and preserve qualified module paths or explicit semantic aliases and façade exports.
A glob import brings every eligible name from another namespace into the current one. If two globs contribute run, the path no longer identifies one function. The failing fixture re-exports both and receives E0659 at dispatch::run().
Ambiguity can arrive after unrelated evolution
The code may compile when only primary defines run. Later, fallback adds a function with the same common name, and a distant glob-using module breaks. This non-local effect is the main maintenance cost of globs.
The official E0659 explanation recommends retaining the module path. Qualification makes primary::run and fallback::run distinct and communicates which policy is selected.
Rust refuses to pick based on import order. A silent precedence rule would make behaviour change when declarations are reordered or dependencies evolve.
The repair keeps domain ownership in the path
The repaired fixture calls both functions through their defining modules. In a real dispatcher, I would choose one according to explicit configuration or expose separately named façade operations.
If a short local name improves readability, explicit aliases work: use primary::run as run_primary. The alias records both the collision and the chosen role. For a public façade, I write an explicit re-export list so code review sees the API change.
The Reference covers use declarations, including aliases and globs. A use creates paths; it does not merge same-named functions into overloads.
Prelude design is the deliberate exception
Libraries sometimes provide a prelude module intended for glob import. A well-designed prelude exports a curated set of frequently needed, low-collision names and treats additions as compatibility-sensitive. It should not simply re-export an entire crate tree.
Traits are often placed in preludes because importing them enables method resolution. Even then, same method names across traits can require fully qualified calls. I document what the prelude contains and keep it small.
Inside tests, use super::* is convenient but can hide where helpers originate. When tests grow or collisions appear, explicit imports improve review. I use globs where the namespace is controlled and the benefit exceeds the evolution risk.
Macros and enum variants add namespace details
Glob-importing enum variants can make pattern code concise, but common variant names such as Error, Ready, or None collide easily. Qualified variants often make matches clearer, especially when several state machines interact.
Macros occupy their relevant namespace and can also become ambiguous through imports. Rust has multiple namespaces for types, values, macros, and lifetimes, so two identical spellings do not always conflict. I read the diagnostic to see which resolved item usage is ambiguous.
Generated modules may add names across versions. Explicit adapter imports keep those changes at the boundary instead of leaking collisions throughout application code.
Public re-exports shape long-term API
A public re-export gives users another supported path. Reorganising internal modules is easier behind a carefully designed façade, but a glob makes that façade change whenever its source module changes.
I list public exports explicitly and test the intended public surface. API-diff tooling can detect accidental additions or removals. If two versions of a dependency need exposure, aliases should encode versions or roles rather than compete for one name.
Fixing E0659 by deleting one glob can remove unrelated public names. I inspect downstream paths and replace required exports explicitly before treating the compile error as solved.
Explicit exports improve documentation quality
Rustdoc can show re-exported items, but an explicit façade gives me a place to explain why each capability belongs together. This matters for discoverability: users should not need to know every internal module, yet they should see stable conceptual groups rather than a flat collection of accidental names.
I review façade additions like other public API changes. A common name such as run gains context either from its module path or from a more specific alias. Keeping that context also improves repository search, compiler diagnostics, and examples. The small cost of writing import lists is paid back when a source module grows without changing unrelated consumers. Globs remain useful for tightly controlled local scopes, but I avoid making them the mechanism that silently designs a library's public vocabulary.
My E0659 checklist
- Which imports or re-exports contribute each ambiguous candidate?
- Did a source module recently add a common name?
- Can qualified paths preserve domain or policy ownership?
- Would explicit local aliases communicate the distinction?
- Is a prelude curated and compatibility-reviewed rather than a crate-wide glob?
- Which Rust namespace contains the collision?
- Does removing a glob accidentally remove other public API paths?
- Can an API-surface test prevent future accidental re-exports?
The core principle is that short names spend namespace clarity. Glob imports spend it in advance on names that do not exist yet. E0659 reveals the debt when two candidates arrive. I restore explicit ownership and keep public façades intentional.