What Swift ABI stability lets a library change
Swift 5 separates runtime compatibility from compiler imports and library evolution. Each solves a different part of distributing compiled Swift code.
A compiled framework can be compatible with an application at runtime and still be impossible for its compiler to import. That distinction explains much of the confusion around Swift 5 and binary libraries. Jordan Rose published ABI Stability and More on February 7 to separate the promises. ABI stability, module stability, and library evolution address different changes to different parts of a build.
The immediate scope is Apple platforms. Swift 5 establishes its stable ABI for macOS, iOS, watchOS, and tvOS. The announcement explicitly leaves other operating systems to separate decisions as their implementations mature. It also describes module stability as work in progress. Reading the announcement as a promise that any binary framework will work with every future compiler would combine two guarantees that the project has deliberately separated.
I find Swift consistently thoughtful about these boundaries. The distinctions require some vocabulary, but they expose decisions that library authors otherwise discover through broken builds. The useful question is which component can change while the others remain fixed.
Runtime compatibility has a specific scope
An application and a library exchange information through compiled conventions. They must agree on how functions receive arguments and how values are represented. Swift can preserve source syntax while changing those compiled conventions, because recompilation can translate the same source into a new representation. A stable ABI removes that freedom at the boundary where already compiled code must continue to interoperate.
The announcement describes an application built with Swift 5 running against the Swift 5 standard library or a compatible future version. That allows the operating system to supply the Swift runtime and standard library. For applications deploying to upcoming Apple operating systems, those components no longer need to accompany each application in its bundle. The benefit concerns deployment as well as compiler engineering.
That statement has two qualifications. The operating system must provide the required components, and the application must target an environment where that arrangement applies. It does not mean every existing deployment target immediately loses its embedded libraries. Nor does it mean a stable runtime somehow gives an older compiler knowledge of declarations introduced later.
The language compatibility mode is another independent choice. Rose states that the binary compatibility guarantee also applies when the Swift 5 compiler uses -swift-version 4.2. A language mode controls the source rules accepted by that compiler. It does not turn the compiler into an older implementation with a different binary contract. This distinction matters when an application adopts a new compiler while retaining older source semantics during migration.
Importing a module happens before execution
To type-check a client, the compiler needs a description of the library it imports. Swift currently distributes that description in an opaque swiftmodule archive tied to the compiler version. The compiled library may obey compatible runtime conventions, but the client compiler still needs to read its declarations. ABI stability cannot make that archive readable by another compiler.
Rose outlined the proposed solution in the earlier plan for module stability. A generated textual interface would describe the public declarations and other information clients require. A consuming compiler could read that text and cache a representation in its own binary format. The stable distribution format and the efficient local import format would have separate responsibilities.
This is an attractive tradeoff because the project can preserve a textual contract while continuing to change its internal compiler data structures. Freezing a serialization of those structures would make implementation details part of a long-term compatibility obligation. The plan also makes the distributed interface inspectable without requiring a tool capable of decoding the originating compiler archive.
Text does not eliminate compilation work. The consumer still has to interpret declarations and reconstruct the information it needs. Caching addresses repeated work after that interpretation. It follows that module stability is not, by itself, a claim that builds become faster. My interest in reducing Swift build times makes that distinction particularly relevant. Compatibility and import performance need separate evidence.
The planned interface also differs from shipping the library source. It describes what clients need to compile against the library. Ordinary implementation bodies can remain absent. Some bodies require different treatment when a library explicitly makes them available for client optimization, which connects module interfaces to the design of inlinable code.
Optimization can preserve old implementation choices
SE-0193, implemented in Swift 4.2, introduces @inlinable and @usableFromInline. An inlinable function exposes its body as part of the module interface. A client compiler may use that body for optimization, including specialization. The annotation does not require the optimizer to inline every call.
For example, this declaration makes a small generic operation available to a consuming compiler under the existing annotation rules.
@inlinable public func repeated<T>(_ value: T) -> [T] {
return [value, value]
}
The relevant promise concerns access to the body. Some callers may incorporate it into their own compiled code, while other callers continue to use the library entry point. Replacing the library later cannot rewrite the code already incorporated into those clients. A library author therefore has to allow old and new implementations to coexist.
The companion attribute permits an internal declaration to be referenced from inlinable code without making it public in the source interface. That declaration still becomes part of the binary interface. Source-level visibility consequently does not describe the complete compatibility obligation. An internal helper can acquire external compiled callers through an exported function body.
This is why library evolution needs deliberate controls. A compiler that knows the exact library version can make assumptions that improve generated code. An independently replaceable library needs room to change without invalidating those assumptions. Rose describes resilience as an opt-in mechanism that balances those requirements, with parts already implemented and further language-process work still needed.
Distribution determines the compatibility requirement
A library built from source alongside an application can change together with its clients. A vendor shipping a compiled framework cannot assume that coordination. The latter needs a compiler-readable interface across toolchain versions, a compatible runtime contract, and appropriate rules for later library changes. Each guarantee covers a different way that the vendor and application author might update independently.
That separation also explains why standard-library deployment is an immediate benefit even while general binary-framework distribution remains unfinished. Apple can supply the runtime components through the operating system under the stable ABI. Independent framework authors still need the module and evolution work appropriate to their own distribution model. A framework dependency that another framework consumes as a binary makes those independent updates more consequential, because recompilation may have to propagate through the dependency chain.