[Discussion] Scoped module identity in Swift

[Discussion] Scoped module identity in Swift

Motivation

Swift requires module names to be globally unique within a build. As the package ecosystem has grown, this has become a practical limitation: it's now common for unrelated packages to export modules with the same name (Core, Utils, Logging, etc.), even when they come from different organizations and serve different domains.

My specific goal is to provide foundational legal tech libraries that law firms can use to provide legal tech products. It seems reasonable that two distinct law firms would want to offer a LawFirmA/swift-agreements and LawFirmB/swift-agreements, and that a client business could depend on both at the same time. This is not currently possible.

This discussion explores:

  • Why this limitation is structural (not just ergonomic)

  • What constraints any solution must respect

  • Two possible directions (incremental vs. architectural)

  • Whether the community wants to pursue either or both

I'm not proposing a concrete solution here. The goal is to validate the problem and discuss what level of solution, if any, is worth pursuing.


The problem

There are two distinct failure modes that are often conflated.

1. SwiftPM package identity collisions

SwiftPM derives package identity from the last path component of the repository URL. Two packages with the same repository name but different owners can collide at resolution time:

dependencies: [
  .package(url: "https://github.com/OrgA/swift-core.git", from: "1.0.0"),
  .package(url: "https://github.com/OrgB/swift-core.git", from: "2.0.0"),
]
// error: duplicate dependency named 'swift-core'

This is a SwiftPM-level issue. See: Failed to resolve two dependencies with the same repository name · Issue #7129 · swiftlang/swift-package-manager · GitHub

2. Compiler module name collisions

Even when package identity is distinct, the compiler still requires module names to be globally unique. If two packages both export a module named Core, they can't be used together:

import Core // ambiguous

What users often want is something like:

import NetworkingCo.Core
import DatabaseCo.Core

There's no way to express this today without consumer-side aliasing (SE-0339), and even that only works under specific conditions.


Why this matters now

SwiftPM has succeeded: the ecosystem is large enough that naming collisions now arise naturally, even with reasonable naming discipline.

The burden falls on application authors who integrate multiple third-party dependencies. In many cases, the conflict is transitive and can't be resolved locally without forking or rewrapping dependencies.


Constraints

Any direction forward has to work within — or explicitly propose changes to — these constraints:

Hard constraints

  • The module namespace is flat

This is a compiler invariant today.

  • Module names are part of the ABI surface

They appear in symbol mangling, .swiftinterface files, and debug info.

  • SE-0339 is limited by design

Module aliasing only works for pure Swift modules, only works for source-built dependencies, and can have reflection implications.

SE-0339 is useful, but it shifts responsibility to downstream consumers, doesn't help with transitive conflicts, and doesn't apply to prebuilt binaries. It's an escape hatch, not a general solution.

Soft constraints

These are design choices rather than fundamental limits.

  • Identity derived from URLs

Other ecosystems decouple identity from fetch location. Go, for example, has modules declare a canonical path in go.mod.

  • No explicit identity declaration

Packages can't currently say "my canonical identity is X" — it's inferred.

  • Loose SwiftPM / compiler coupling

Package identity and module identity are related but not formally unified.


Prior art

Other ecosystems addressed this early:

  • Go — Module path is identity (import "github.com/org/pkg"), with canonical paths declared in go.mod.

  • npm — Scoped packages (@org/package) provide organization namespaces.

  • Rust — Dependency renaming in Cargo.toml; RFC 3243 proposes package namespaces like tokio::sync.

  • Python — Flat namespace with well-documented collision problems; a cautionary tale.


Two possible directions

Path A: Qualified selectors (incremental)

Allow qualified imports while keeping canonical module identity flat:

import NetworkingCo.Core
import DatabaseCo.Core
let client = NetworkingCo.Core.Client()

Under the hood, this could be implemented via synthesized aliases. The qualifier need not be a URL — it could be an organization identifier, a declared scope, or something else.

Properties:

  • Builds on SE-0339-style mechanics

  • Works for pure Swift, source-built modules

  • No immediate binary framework support

Pros:

  • Incremental, fits within today's architecture

  • Solves the most common composition problems

Cons:

  • Introduces a distinction between canonical identity and import spelling

Path B: Hierarchical module identity (architectural)

Redefine module identity itself as structured rather than flat:

// Canonical identities
NetworkingCo.Core
DatabaseCo.Core

This would require changes across the compiler, ABI, tooling, and interfaces, plus a governance model for scopes and a migration strategy.

Pros:

  • Cleaner model, no aliasing duality

  • Aligns with Go and npm

Cons:

  • Large scope, long timeline

  • Comparable to Swift Concurrency or Macros in effort

Path A and Path B aren't mutually exclusive. Path A may be valuable even if Path B is ultimately desired.


Non-goals

To keep this discussion focused:

  • Binary framework support (requires ABI work beyond near-term scope)

  • Objective-C / C / C++ interop (symbol collision issues are orthogonal)

  • Syntax details (. vs / vs ::)

  • Automatic migration


Questions for discussion

  1. Is flat module identity becoming an ecosystem-level problem, or is SE-0339 sufficient in practice?

  2. Is there interest in a long-term move toward hierarchical module identity?

  3. If so, is an incremental solution worth pursuing in the meantime?

  4. If scopes exist, who owns them — registry, domain, or package declaration?

  5. Which workgroup should drive this discussion?


Related discussions


Closing

I'm asking whether this is a problem the community wants to address, and at what level. Depending on interest, I'm happy to follow up with a focused pitch for Path A, a vision document for Path B, or both.


Acknowledgments

Thanks to @Joannis_Orlandos for discussions that helped shape this write-up, and to the authors of SE-0292, SE-0339, and the Module Selectors pitch for prior work in this space.

4 Likes

If by "aliases" you mean "module aliases" of the SE-0339 variety, how would this actually work? The way module aliases work is that at the time Core is compiled, it is given a different ABI name, and the compiler knows to swap the real name for that one when a client who is also aware of that alias imports it.

Just saying that the client module will synthesize an alias isn't enough—by that time, the other module has already been compiled and its ABI name is locked in.

Or is your suggestion that the build system (i.e., Swift Package Manager) would be responsible for globally applying some additional identifier, like the package name, to all Swift modules that it builds in the graph?

1 Like

That’s a fair point. For clarity, I didn’t mean that a downstream client could synthesize an alias after Core has already been compiled. I agree that wouldn’t work once the ABI name is locked in.

What I had in mind for Path A was that any aliasing would have to happen at build-plan time, i.e. the build system assigning distinct identities to colliding modules when they’re compiled, and then making that mapping available to dependents. The qualified import (NetworkingCo.Core) would just be the surface syntax for selecting between those identities.

Whether SwiftPM is the right place to do that, what the qualifier should be derived from, and how much of this should be exposed in interfaces or tooling are all still open questions.

I agree that the inability to name multiple packages the same name is quite frustrating and limiting. There's much prior art to name libraries swift-xyz like swift-html for example. If multiple authors want to add an html library and gain enough adoption is the ecosystem, they cannot coexist anymore. That is a growing problem that is increasingly limiting. Right now, the first author to pick their preferred name is "the winner" usually. But I don't like that much.

1 Like

CC @dschaefer2 curious what your thoughts are on this, and whether you think it'll be a problem or not.

1 Like