Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-470 · Case file with fixtures · Case 442 of 694 · Compiler evidence

Two Rust Imports Cannot Claim the Same Local Name

Imports create local aliases, and one scope cannot resolve the same alias to two items. Keep one path qualified or rename imports by domain role rather than origin-only or numbered suffixes.

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
Each use declaration creates a local alias from its final path component, leaving run unable to resolve to one item before call type checking.
First discriminating check
Keep paths qualified or alias imports by domain responsibility, and inspect glob imports and public re-exports before choosing the stable local name.

I imported first::run and second::run into the same module. Rust emitted E0252 because both use declarations attempted to create the local value name run.

The failing fixture shows the collision at the import boundary. Both functions remain valid at their qualified paths; only the shortened local alias is ambiguous.

use creates a local name

A use declaration does not copy code or merge functions. It binds an alias in the current scope. The use-declaration Reference explains that imported paths become locally accessible under their final component unless renamed.

With two aliases called run, the expression run() cannot identify one item. Rust does not choose by import order or by the body expected at the call site.

E0252 makes this ambiguity explicit before behaviour can depend on incidental ordering.

Rename by responsibility

The repaired fixture writes:

use first::run as run_first;
use second::run as run_second;

In real code, I prefer names such as run_migration and run_health_check over source-order labels. The local alias should tell a reader why this caller uses each operation.

If the modules already have meaningful names, keeping qualified calls such as migration::run() may be even clearer.

Qualification avoids unnecessary aliases

I often import the modules and leave the repeated function name attached:

use crate::{migration, probe};

migration::run();
probe::run();

This preserves API vocabulary while adding context at each call. It also reduces the number of invented aliases a reviewer must remember.

The best choice depends on call frequency and whether the module name is already concise.

Rust does not overload imported free functions

Even if one run accepts a path and another accepts a socket, Rust will not select between them by argument type. Free functions are resolved by path first and type-checked afterward.

If type-directed shared behaviour is real, a trait method may model it. If operations are simply different, distinct paths remain more honest.

I do not create a trait only to preserve one short verb in a small scope.

Namespace kind matters

The namespace Reference separates type, value, macro, lifetime, and label names. Imports can introduce aliases into the namespaces occupied by the imported item.

Some same-spelling items coexist because they live in different namespaces. Two functions do not: both occupy the value namespace here.

I read the diagnostic's item kinds when a collision seems surprising, especially with tuple constructors and derive macros.

Glob imports make collisions less visible

use first::*; use second::*; can introduce conflicts far from the names a reader sees. Adding a new public item to either module may break downstream users who glob-import both.

I avoid broad glob imports in production modules with multiple dependencies. Explicit imports document the actual surface and make code review of name changes easier.

Tests and preludes can be exceptions, but their collision risk should be understood.

Re-exports turn aliases into public API

With pub use, the chosen local name also becomes part of the module's outward surface. Renaming to fix E0252 can therefore be a compatibility change for downstream callers.

I check whether the import is private convenience or public re-export. For public façades, role-based stable names and deprecation paths may be required.

My E0252 checklist

I also review error types with special care. Importing two crates' Error types under vague aliases can spread translation details through the module. Often I keep each path qualified at the integration boundary and convert both into one local domain error. This solves the naming collision while improving layering. The aliases then remain close to the foreign APIs instead of becoming vocabulary used throughout the application.

For tests, I compile the module after adding or removing glob imports because a new dependency release can expose another public name without changing my source. Explicit import lists make that change intentional.

  • Which two import paths create the same final local name?
  • Are both items actually needed in this module?
  • Would keeping one or both paths qualified read better?
  • What domain roles should aliases communicate?
  • Am I expecting signature-based function overloading?
  • Did glob imports hide the collision source?
  • Do the items occupy the same namespace?
  • Is either import a public re-export with compatibility impact?

The core principle is that an import is a local name-resolution decision. I keep each decision unambiguous and preserve source or domain context where repeated API vocabulary such as run, open, or Error would otherwise collide.