Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-469 · Case file with fixtures · Case 441 of 694 · Compiler evidence

Rust Items Cannot Be Defined Twice in the Same Namespace

Each module namespace needs an unambiguous item identity. Remove accidental duplication, give distinct operations role-based names, use traits for type-directed behaviour, and make cfg branches mutually exclusive.

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
One scope needs one resolvable item identity, while both declarations or simultaneously active cfg branches compete for the same namespace entry.
First discriminating check
Find both producers, compare behaviour and history, then remove a stale duplicate or separate legitimate policies by name, type path, trait, or exclusive cfg.

I declared two module-level functions named decode. Rust emitted E0428 because both definitions occupy the same value-namespace name in one module.

The failing fixture also disproves an assumption familiar from some languages: Rust does not choose a free function by parameter or return-type overloading.

One namespace entry needs one identity

Functions, constants, statics, tuple constructors, and several other declarations live in the value namespace. The namespace Reference permits the same spelling in some different namespaces, but not duplicate bindings in one relevant namespace and scope.

The E0428 explanation describes duplicate type or module definitions; current rustc also reports the code for duplicate functions such as this fixture.

The compiler must resolve decode() without guessing which body I intended.

Give different operations semantic names

The repaired fixture renames the functions decode_header and decode_payload.

This is stronger than decode1 and decode2. Call sites state what structure they expect, search results group the right work, and later signatures can evolve independently.

When the two definitions were accidental copies, I remove one only after comparing bodies and callers so unique fixes are not lost.

Rust uses traits instead of ad hoc overloading

If several types share a conceptual operation, a trait method can provide type-directed implementations:

trait Decode {
    fn decode(input: &[u8]) -> Self;
}

Each target type implements the contract, and callers provide or infer Self through a meaningful type context. I do not introduce a trait solely to reuse one common verb if ordinary names are clearer.

The absence of signature overloading keeps name resolution and function values predictable.

Inherent methods can share names across types

Header::decode and Payload::decode are distinct associated paths, so each type may offer its own method. This often fits constructors and parsers because the output type owns the operation.

Within one type, duplicate inherent methods with overlapping applicability remain a conflict. Generic impl blocks need care because apparently different bounds may still overlap for some instantiation.

I choose associated methods when type ownership clarifies the call, not merely to hide duplicate free functions.

cfg branches must be mutually exclusive

Platform implementations often use the same public name under different #[cfg] attributes. E0428 appears if two conditions are active together:

#[cfg(unix)]
fn backend() {}

#[cfg(feature = "portable")]
fn backend() {}

On Unix with the feature enabled, both exist. I make selection conditions explicitly exclusive or move implementations into separate modules and choose one module alias.

I test feature combinations, not only each feature alone.

Macro expansion can duplicate items invisibly

Two macro invocations may generate the same function, struct, or module name even though the source text contains no visible duplicate declaration. Derive helpers and schema generators can collide after identifier normalisation.

I inspect expansion or reduce invocations until the two producers are clear. The generator should detect collisions from source identities and produce a domain-level message where possible.

Appending unstable numeric suffixes is rarely a maintainable public API.

Two use declarations importing the same short name can produce E0252 rather than E0428. The mechanism is similar—one scope receives ambiguous aliases—but the repair may be qualification or as renaming rather than changing declarations.

I read whether rustc points at definitions, imports, or macro expansions. The error code helps narrow the layer.

My E0428 checklist

Duplicate names can also signal two subsystems being merged without an ownership decision. Before selecting a surviving implementation, I compare error behaviour, telemetry, side effects, and callers—not only return types. If both versions encode legitimate policies, I name those policies and choose between them at a higher layer. Silently deleting one based on file order can remove production behaviour that was hidden behind a stale module or configuration. The compiler has found the collision; the repository history explains why both definitions exist.

  • Which two declarations create the same namespace name?
  • Is one an accidental stale copy?
  • Do the operations deserve role-based names?
  • Would associated methods or a real trait model shared behaviour?
  • Can two cfg conditions become true together?
  • Did macros generate one of the duplicate items?
  • Is the collision actually between imports and aliases?
  • Have all feature combinations been compiled?

The core principle is that a source-level name resolves to one item identity within its namespace. I make different behaviour different by domain role, type path, trait contract, or exclusive configuration instead of asking Rust to guess from signatures.