[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 ingo.mod. -
npm — Scoped packages (
@org/package) provide organization namespaces. -
Rust — Dependency renaming in
Cargo.toml; RFC 3243 proposes package namespaces liketokio::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
-
Is flat module identity becoming an ecosystem-level problem, or is SE-0339 sufficient in practice?
-
Is there interest in a long-term move toward hierarchical module identity?
-
If so, is an incremental solution worth pursuing in the meantime?
-
If scopes exist, who owns them — registry, domain, or package declaration?
-
Which workgroup should drive this discussion?
Related discussions
-
Any plans for SwiftPM to include owner name in package identity? (2024)
-
[Pitch] Module selectors (2025)
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.