Preparing for Swift 6

Chris Mash
8 min readJan 23, 2024
A poorly generated cartoonish AI image of a 5 and 6 merged into a single numeral, spraying a water on an unsuspecting person holding an umbrella

First things first. Let’s address that bizarre image… I used Bing’s AI image creator to try and show a number 6 fighting a number 5, to represent the breaking changes Swift 6 is bringing. But it seems the AI struggled with that and decided to give me this unholy abomination instead, which I feel deserves to be shared as far and wide as possible!

When is it coming?

Swift 6 will bring numerous breaking changes to iOS, which is something Apple have been preparing for quite some time. They’ve been implementing breaking changes in Swift 5, hidden behind feature flags, which will become the default behaviour in Swift 6.

It’s been under discussion since at least January 2020 but there’s actually still no announcement on when it will be released.

The following are some key posts from the Swift forums relating to Swift 6:

January 2020: On the road to Swift 6
January 2023: Design Priorities for the Swift 6 Language Mode
November 2023: Progress toward the Swift 6 language mode

Swift has been consistent with a cadence of March and September for major/minor releases. September 2023 saw 5.9 released and 5.10 has already been mentioned, which will presumably come along in March 2024.

Perhaps Swift 6 could come along in September 2024? Maybe we’ll see it announced at WWDC, giving us a few months to get fully prepared? Though that’s a wild guess really, more years might roll by before we see it released!

What do we need to do?

To get your code ready for Swift 6 you can start enabling the various feature flags Apple have provided in Swift 5 and fixing the warnings or errors produced.

Ensure you enable the feature flags on all targets: framework, app, unit/UI tests etc. And also make sure you clean the targets and build/run them (test targets often don’t actually build until you run them) to get Xcode to report any warnings or errors.

Inspect and clean up anything it reports and you’ll be in a much better place to adopt Swift 6 when it’s finally released! If possible you should keep the feature flags enabled so that any new code you write before Swift 6 comes along will be written compatible first time!

If necessary you can check if the -enable-experimental-feature flags are enabled or not in your code with hasFeature (read more here). Even when Swift 6 is adopted hasFeature will report true so you don’t have to worry about it thinking the feature flags don’t exist anymore! In some situations this might help to prepare code early if it requires any kind of incompatible change as you can have both old and new code present at the same time.

Things we can opt into early

Progress toward the Swift 6 language mode calls out various things we can already opt into voluntarily.

I’ve summarised everything I could find here for you and provided a bit of a summary and high level assessment. Bear in mind that I’ve not fully digested everything I’m linking too and not played around with many of these feature flags so take it all with a pinch of salt!

SE-0274: Concise magic file names

Summary: #file will evaluate to <module name>/<filename> rather than a full file path. #filePath is introduced if the full file path is still required

Compiler flag: -enable-experimental-feature ConciseMagicFile or -Xfrontend -enable-experimental-concise-pound-file

The latter is mentioned as a prototype (not exactly the same as the final intention). It’s unclear whether that applies to the experimental feature flag too or not.

High level assessment: Seems unlikely to cause issues for most projects unless there’s anything very specific about any #file usage

SE-0286: Forward-scan matching for trailing closures

Summary: Trailing closure matching scans backwards through parameters to find a match but will instead scan forwards, which may cause changes in behaviour if a function has multiple closure parameters (with default values) of the same type and the trailing closure syntax is used.

Compiler flag: -enable-experimental-feature ForwardTrailingClosures

High level assessment: Currently not clear what the decision for Swift 6+ is going to be, so hard to assess the impact as there may be none. It may be worth assessing risky usage of trailing closures and updating to be explicit.

SE-0337: Incremental migration to concurrency checking

Summary: Swift 5.5 introduced the Sendable protocol to help eliminate data races, though it was not enforced, in 6 it will be

Build setting: Strict Concurrency Checking
Compiler flag:
-enable-upcoming-feature StrictConcurrency or -warn-concurrency

High level assessment: This is one of the biggest impacts. Properly conforming to Sendable isn’t always straight forward and you don’t need to be using async/await for there to be errors! A project I work on had 600+ errors when enabling complete concurrency checking. There’s some useful additional reading on SwiftLee.

SE-0352: Implicitly Opened Existentials

Summary: Improvements to existentials to make them easier to convert back into generics, removing some compiler errors that could currently be encountered

Compiler flag: -enable-upcoming-feature ImplicitOpenExistentials

High level assessment: For the most part seems like it likely won’t have any effect, but it’s possible that a different overloaded function might end up getting picked with the new behaviour

SE-0354: Regex Literals

Summary: New support for defining regex literals with /…/ will be enabled in Swift 6, which could clash with some comment blocks etc.

Build setting: Enable Bare Slash Regex Literals
Compiler flag:
-enable-upcoming-feature BareSlashRegexLiterals

High level assessment: Should be simple enough to enable ahead of time and see if anything breaks, modifications should be straight forward

SE-0383: Deprecate @UIApplicationMain and @NSApplicationMain

Summary: Use of UIApplicationMain becomes an error in Swift 6 (already meant to be a warning in Swift 5, though it’s not coming up as one for me!)

Compiler flag: -enable-upcoming-feature DeprecateApplicationMain

High level assessment: Trivial to replace it with @main

SE-0384: Importing Forward Declared Objective-C Interfaces and Protocols

Summary: Improve usage of ObjC libraries from Swift code around forward declarations

Compiler flag: -enable-upcoming-feature ImportObjcForwardDeclarations

High level assessment: Unlikely to be an issue for most, just a benefit. Currently workarounds would be in place to make the ObjC code usable from Swift and these could be removed from Swift 6, potentially speeding up build times a little.

SE-0401: Remove Actor Isolation Inference caused by Property Wrappers

Summary: Property wrappers currently infer actor isolation, which can be confusing, they won’t in Swift 6

Compiler flag: -enable-upcoming-feature DisableOutwardActorInference

High level assessment: Any use of property wrappers might cause a change in actor isolation in related code, which could cause compilation errors that need resolving.

SE-0409: Access-level modifiers on import declarations

Summary: Ability to keep dependencies internal to a module

Compiler flag: -enable-experimental-feature AccessLevelOnImport
or maybe -enable-experimental-feature InternalImportsByDefault

High level assessment: Shouldn’t be too much impact. Imports will be internal by default so there might be places where you need to add some additional imports to fix build errors. You might see a bit of a speed up in build times due to the compiler having to do less work.

Potentially testable in Swift 5.10 (March 2024)

The following are mentioned on Progress toward the Swift 6 language mode, and while the wording seems open to interpretation it looks like these feature flags might be getting added in 5.10.

SE-0411: Isolated default values

Summary: Actor isolation rules will be changing for default arguments and stored property values

Compiler flag: -enable-upcoming-feature IsolatedDefaultValues

High level assessment: This could potentially introduce some fun errors to handle

SE-0412: Strict concurrency for global variables

Summary: An improvement to strict concurrency to cover global variables

Compiler flag: -enable-experimental-feature GlobalConcurrency

High level assessment: This could potentially introduce some fun errors to handle

SE-0418: Inferring @Sendable for methods and key path literals

Summary: A concurrency improvement that will infer whether functions and key path literals are sendable or not (currently you have to do some workarounds)

Compiler flag: -enable-experimental-feature InferSendableFromCaptures

High level assessment: Doesn’t sound like it should have any negative impact, could just make adoption of Sendable easier?

SE-0414: Region based isolation

Summary: A concurrency improvement that will allow non-Sendable values to be transferred if the compiler can determine a data race isn’t caused

Compiler flag: -enable-experimental-feature RegionBasedIsolation

High level assessment: Sounds like zero impact and would just make Sendable adoption easier

SE-????: Improved control over closure actor isolation

This one’s not on the main branch and the linked proposal is in draft, so not sure if it was included on Progress toward the Swift 6 language mode in error or has perhaps been folded into another proposal or just abandoned? (the link to the pitch is also broken)

Summary: A concurrency improvement to give the ability to define the actor isolation of a closure

Compiler flag: Unknown

High level assessment: Though it’s just a draft it seems like there’s nothing to cause issues in there, just a change that would make it easier to introduce concurrency

Not expected in Swift 6

SE-0335: Introduce existential any

This was previously going to be enabled by default in Swift 6 but has been deferred to further in the future, according to Progress toward the Swift 6 language mode.

Summary: All existentials will need to be prefixed with any. More info on SwiftLee.

Compiler flag: -enable-experimental-feature ExistentialAny

So when should I be doing all this?

Swift 6 doesn’t seem like it’s going to provide forwards compatibility, meaning a dependency built with Swift 6 won’t be compatible with a consuming library built with Swift 5. For example if you use a library like Firebase and they make a release built with Swift 6, but you’re still using Swift 5 that’s not going to compile.

The opposite should be fine though. If you start using Swift 6 then you’ll still be able to use libraries that are built with Swift 5.

So an instant adoption of Swift 6 isn’t necessary for everyone, most likely. But you may find the need to adopt it somewhat forced upon you by a dependency you use. But that’s only if you need to upgrade to their latest release once they adopt Swift 6, you can continue to use their older Swift 5 releases until you absolutely need that Swift 6 release. Though that may be sooner than you anticipate if there’s some new feature or fix you need from it!

Regardless of when you decide to adopt Swift 6, anything you can do to reduce the amount of work it would take, ahead of time, is likely sensible in case you find yourself forced into adopting it at short notice.

--

--

Chris Mash

iOS developer since 2012, previously console games developer at Sony and Activision. Twitter: @CJMash