As we prepare for Swift 6 (!) 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.
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:
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.
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
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.
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