Mehdi Akiki
Rust Failure Atlas / Cargo and dependencies

RFA-473 · Case file with fixtures · Case 445 of 694 · Compiler evidence

Two External Rust Crates Cannot Share One Local Alias

Dependency aliases are source-level identities. Give each external crate one distinct name, remove unnecessary legacy extern declarations, and keep Cargo renames stable when multiple package versions or ecosystems coexist.

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
A source alias identifies one external crate throughout its path tree, so sharing it would make every core-prefixed lookup ambiguous.
First discriminating check
Inspect Cargo package, crate, alias, and version identities, remove obsolete declarations, or choose stable role/version names for an intentional dual dependency.

I declared extern crate core and then aliased std as core. Rust returned E0259 because two external crates cannot have the same local root name in one module.

The failing fixture is artificial but isolates a real dependency-management principle: a crate alias is part of how source code identifies an external API.

An alias names one crate identity

After extern crate std as core, paths beginning core:: would need to choose between the actual core crate and the aliased standard library. Rust rejects the ambiguity instead of using declaration order.

The E0259 explanation repairs this by choosing another name. The extern-crate Reference defines the optional rename.

Names are local, but the selected identity affects every path below them.

Use distinct aliases with a reason

The repaired fixture aliases std as standard. In ordinary edition-2024 code, I would normally use the conventional std and core names without explicit declarations.

Custom aliases are useful when package names conflict, when migrating ecosystems, or when two versions are intentionally compared. I name the distinction, such as codec_v1 and codec_v2, rather than codec2 with no lifecycle meaning.

Comments should explain temporary dual-version arrangements.

Cargo renames dependencies before source uses them

Cargo supports a dependency key that differs from the package name. The Cargo dependency Reference calls this renaming.

For example, a manifest key can provide old_codec while package = "codec" selects the published package. Source code uses the dependency key as the crate name.

I inspect Cargo.toml and the resolved graph before assuming an unexpected source alias identifies a different package.

Modern editions reduce explicit declarations

Edition 2018 and later make Cargo dependencies available through the extern prelude, so extern crate is seldom needed. Removing legacy declarations can simplify name resolution.

However, macro imports from old-edition code and special runtime setups may still rely on explicit syntax. I use cargo fix --edition and tests rather than deleting declarations blindly.

The fixture pins edition 2024 but deliberately uses legal legacy syntax to reproduce the diagnostic.

Multiple versions need type-boundary planning

Cargo can resolve two versions of one package transitively. Directly naming both with aliases can support migrations, but their same-looking public types are distinct identities.

An old_codec::Message is not a new_codec::Message just because fields match. I convert at a narrow boundary or serialise through an agreed format instead of spreading both versions across business logic.

The alias prevents path collision; it does not solve type compatibility.

Public APIs should avoid leaking temporary aliases

If a public function exposes types from an aliased transitional dependency, downstream users may become coupled to that version. I keep adapters private where possible and expose stable domain types.

For libraries, Cargo's package rename is an implementation detail only until dependency types appear in the public signature.

This is why an E0259 repair can require more than a spelling change.

Build scripts and proc macros see their own dependency graphs

A build dependency and a normal dependency can use similar package names while compiling for different hosts or targets. Source aliases live in the crate being compiled, so I locate whether the collision belongs to the main library, a build script, or a procedural macro crate. Fixing the application import does not change a separately compiled host tool.

Workspace-wide searches can otherwise make the wrong manifest look responsible.

Lockfiles reveal accidental duplication

When two versions were not intentional, cargo tree -d helps expose duplicated packages and their dependants. Unifying compatible requirements can remove the need for source aliases and reduce compile time or binary size.

I do not force a version unification across incompatible constraints without checking semver and behaviour. The graph is evidence for the decision, not an instruction to pick the newest version blindly.

My E0259 checklist

I record the package name, crate name, source alias, and version separately. They often differ, and treating them as one label creates confusing migration notes. This small inventory is especially useful when a package exports a library name unlike its registry package or when a dependency is renamed in the manifest.

  • Which two external crates receive the same local name?
  • Is either explicit extern declaration still necessary?
  • Does Cargo already rename one dependency key?
  • What distinction should a source alias communicate?
  • Are multiple package versions intentional or accidental?
  • Do types cross between the aliased versions?
  • Does a public API expose a transitional dependency identity?
  • Can the dependency graph be simplified instead?

The core principle is that crate aliases are identities, not cosmetic abbreviations. I give each dependency a stable, meaningful root and contain multi-version migrations so callers do not have to reason about ambiguous or leaking package histories.