February 16, 2017·5 min read·engineeringswiftcompatibilityreleases

Swift 4 plans migration one module at a time

Swift 4 plans two language modes under one compiler, letting modules migrate separately. The release process makes source compatibility an explicit condition of stabilization.

A language migration becomes harder when every dependency has to move with the application. Swift 4 plans to reduce that coordination by letting modules choose different language modes under the same compiler. An application can adopt the new language while a framework it imports continues to use Swift 3 source. That changes the unit of migration from the whole dependency graph to an individual target or package.

Ted Kremenek describes that design in the February 16 release plan. Swift 4 is intended for fall 2017, with source stability for Swift 3 code and essential work toward binary stability. The distinction between those goals matters. The plan promises interoperability between modes when the modules use the same compiler. It does not announce that arbitrary binaries built with different compiler versions can interoperate.

I find Swift consistently well designed, and this is a useful application of that care to adoption. Language improvements create less disruption when existing dependencies can remain unchanged during an application migration. For the small applications I write, build time is already an unwelcome cost. Requiring unrelated source repairs at the same time makes a compiler upgrade harder to justify.

Language mode belongs to the module

The planned compiler has two modes, selected with -swift-version 3 and -swift-version 4. Swift 3 mode is the default for existing code. Its compatibility target is the vast majority of source accepted by the Swift 3.1 compiler. Swift 4 mode enables disruptive changes, including the planned overhaul of the String API. That work aims to improve API ergonomics and performance, but it requires migration for affected source.

The announcement gives an application and framework example. The application target can use Swift 4 mode while a separate framework target uses Swift 3 mode. Both targets can participate in the same binary when built by the same compiler. A Swift 4 package should likewise be able to depend on packages whose source remains in Swift 3. These examples describe intended behavior for the release, not an assertion that the final implementation is already available.

This arrangement separates two decisions that a single version label can obscure. Choosing the compiler determines which implementation builds the program. Choosing a language mode determines which source rules a module uses. A project can standardize its compiler while allowing targets to migrate at different times. The same-compiler condition remains important whenever a framework arrives as a compiled dependency instead of source.

The release plan expects the source difference between Swift 3 and Swift 4 to be much smaller than the earlier transition from Swift 2.2 to Swift 3. That is an expectation about migration scope, not a statement that the String changes are compatible. The mode boundary gives those changes a defined place without requiring every existing module to accept them immediately.

Compatibility permits corrections to invalid programs

Swift 3 mode has a strong compatibility goal, but the plan allows compiler fixes that reject code which should never have compiled. Kremenek expects such cases to be relatively rare. Unexpected rejection of previously compiling Swift 3.1 source should become a bug report, so maintainers can distinguish an intentional correction from a compatibility regression.

That exception has a specific implication for application owners. A successful build with the older compiler is evidence that the compiler accepted the source. It does not establish that every accepted construct was valid according to the intended rules. Conversely, calling a rejection a compiler correction requires an explanation of the invalid construct. The exception should not make every migration failure appear inevitable.

The Swift 3.1 release process established the immediate precedent. That release aims to preserve Swift 3.0 source while adding limited language improvements and work on Linux and package management. Its January 16 cutoff shifts mainline development toward Swift 4. The February plan continues the compatibility policy while adding an explicit place for source-breaking work through the second mode.

The release branch has its own admission rules

Swift 4 development happens on master until a final branch date, expected in early summer, is announced. Before that cutoff, the plan calls for approximately fortnightly merges into swift-4.0-branch. Snapshots and the final release come from that release branch. Individual pull requests can carry selected changes between the larger merges.

The interval creates time between active development and the branch used for downloadable builds. It also means that the contents of mainline and a release snapshot need not match at a particular moment. A report about a snapshot needs its toolchain identity, because a change already merged into mainline might not yet be present in the release branch.

Swift Package Manager is an exception to the fortnightly schedule. Its mainline changes merge into the release branch daily. The LLVM, Clang and LLDB repositories have already branched and will not branch again. Swift therefore has a coordinated release across repositories with different integration schedules. The compiler branch alone does not fully describe the toolchain being assembled.

After the final branch date, mainline moves toward the next release and Swift 4 enters a stabilization period. Only selected critical fixes enter the release branch. The acceptance criteria become increasingly restrictive as the release converges. A change can be useful for Swift while still being unsuitable for that late stage of this particular release.

Snapshots connect the policy to actual source

Daily downloadable snapshots continue the approach announced for Swift 3.1. Continuous integration produces them when tests pass, while final builds remain a separate publication when the release is complete. This gives outside package maintainers a more frequent opportunity to find incompatibilities than irregularly spaced developer previews.

I value that feedback path because compatibility depends on programs the compiler team does not own. The mode design defines the intended boundary, but actual dependencies reveal whether the implementation respects it. A package author can report a specific Swift 3.1 program rejected by a specific Swift 4 snapshot without first migrating that package to Swift 4 mode.

Release-branch nominations must explain the issue, assess the change's scope and identify its specific release risk. They must also describe testing performed or still needed, with an associated issue when applicable. Code owners review affected components, and the relevant release manager accepts changes outside automatic merges. Language and API changes continue through Swift Evolution. Those requirements give compatibility failures a route into stabilization while keeping the decision tied to the affected source and the proposed repair.