Swift 6 language mode: pros and cons of adopting?

As we prepare for Swift 6 (:tada::tada::tada:!) I've seen a lot of talk and efforts to start migrating existing projects to strict concurrency.

It does, however, sound like there will be an option in Swift 6 to use the "Swift 5 language mode" which will not surface data race safety errors and warnings. This sounds like a great compromise since some projects may need more time to migrate or might not need the data race safety compiler features.

My question is: are there other downsides to not adopting the Swift 6 language mode, other than lack of the new data race safety features?

I'm hoping that knowing the specifics will help folks considering preemptive migrations make informed decisions.

Thanks in advance :smile:

I would say lack of race safety is the primary downside and the major one here. Given that a lot of the code will use new concurrency (e.g. we can see that Apple platforms are adopting it fast), you'll lost a lot of compiler checks, that also has to guide through tough parts.

The major complication with Swift 6 mode is a lot of errors for what is right now is warnings, since not all dependencies is ready for new concurrency. Which still means you need at least use Swift 5 mode with all the features turned in. If there is nothing from holding you back to start adoption early, I am advising to turn all the checks and features right now and prepare for Swift 6. Otherwise, you might end up in more unpleasant situation when significant parts of the project require redesign to migrate them.

there are ways to prepare for Swift 6 without actually adopting the Swift 6 language mode. for example, you could selectively enable Swift 6 features by setting the appropriate enableUpcomingFeature / enableExperimentalFeature flags. these will surface some Swift 6-specific problems and make the actual migration easier.

here is what i am using across nearly all of our projects already:

settings.append(.enableUpcomingFeature("BareSlashRegexLiterals"))
settings.append(.enableUpcomingFeature("ConciseMagicFile"))
settings.append(.enableExperimentalFeature("StrictConcurrency"))
4 Likes

I believe the full set of upcoming feature flags available in Swift 5.10 is:

    .enableUpcomingFeature("BareSlashRegexLiterals"),
    .enableUpcomingFeature("ConciseMagicFile"),
    .enableUpcomingFeature("ForwardTrailingClosures"),
    .enableUpcomingFeature("ImportObjcForwardDeclarations"),
    .enableUpcomingFeature("DisableOutwardActorInference"),
    .enableUpcomingFeature("ExistentialAny"),

Plus one experimental feature flag:

.enableExperimentalFeature("StrictConcurrency")

The upcoming feature flags BareSlashRegexLiterals , ForwardTrailingClosures, ImportObjcForwardDeclarations, and DisableOutwardActorInference enable things that in very rare circumstances cause source-level breakage.

Most projects should be able to enable these four with no changes at all.

Turning them on in your projects (or even target by target) gives you the ability to see if changes need to be made now, instead of waiting for Swift 6 language mode.

More information about upcoming feature flags is available in this post on Swift.org:
Using Upcoming Feature Flags

3 Likes

According to the swift-list-features script I wrote (source code here), there are three more upcoming feature flags in 5.10, namely:

.enableUpcomingFeature("DeprecateApplicationMain"),
.enableUpcomingFeature("GlobalConcurrency"),
.enableUpcomingFeature("IsolatedDefaultValues"),
7 Likes

Thanks @ole! It slipped my mind that the project I took those from needs to remain on Swift 5.9 for the time being.

1 Like

How do you toggle this features on an Xcode project target level, somewhere in build settings?
If that is possible, are they implicitly set on SPM dependencies or do we need to manually set them per dependency?
Is there a way to debug if a Module has this enabled, like a boolean flag to make sure it is using a feature?

I believe the compiler will use the tools version set in each pacakge's manifest, otherwise it could cause chaos if dependencies that aren't ready start getting compiled in Swift 6 language mode

1 Like

For concurrency Xcode right now has its own setting ("Strict Concurrency"), the rest can be added via "Other Swift flags" in build settings. As for SPM, it is a bit tricky: I believe general intention was for packages to inherit, because sometimes it works, but it is better to explicitly set this settings within each package as well.

1 Like

so you would add it as --ExistentialAny in other swift flags, what is the right format?

The syntax is -enable-upcoming-feature ExistentialAny. (Or -enable-experimental-feature StrictConcurrency for strict concurrency, but as mentioned, Xcode has a separate setting for that.)

In Xcode, keep in mind that the two components should be treated as separate arguments, so -enable-upcoming-feature and ExistentialAny must be two separate entries in the list under Other Swift flags, not one single item. For each additional feature you want to enable, you have to repeat -enable-upcoming-feature, followed by the name of the feature flag.

By the way, these compiler flags are documented in SE-0362: Piecemeal adoption of upcoming language improvements, but it's a shame that information like this isn't part of the normal Swift documentation (AFAIK). IMO, people should be able to search the Swift docs for "feature" or "feature flag" or "ExistentialAny" and get to a page where all of this is explained, including a list of available feature flags.

The flags must be enabled per target. They don't automatically propagate to a target's dependencies. This is a good thing because you may want to build different targets with different flags, e.g. to adopt breaking changes step by step. (In Xcode, the standard propagation rules apply. If you set the flags on the project's build settings and the default for targets is to inherit he parent settings, then the targets will inherit them. This is no different than any other build setting in Xcode.)

The easiest way is probably to write some previously invalid code that becomes valid through the feature, or previously valid code that gets diagnosed with the feature flag because it's now invalid. E.g. for ExistentialAny:

let x: CustomStringConvertible = ""

This code should trigger an error (or warning?) when the feature flag is activated.

There is also this compile-time check:

#if hasFeature(ExistentialAny)
    print("Feature flag is activated")
#endif
6 Likes

Thank you for the detailed explanation @ole it was really helpful!

1 Like

I am always confusing this in Xcode settings, so add visualisation if someone would struggle to get it right as I do :slight_smile:
Screenshot 2024-05-13 at 18.06.17

2 Likes

A reminder that ExistentialAny will not be enabled by default in the Swift 6 language mode.

3 Likes

The post I mentioned Using Upcoming Feature Flags has full details on how to use them with Xcode, including screen shots of where to put them.

1 Like