Preparing My App for Swift 6

How to enable Swift 6 mode for your Xcode projects and for your SwiftPM modules today. And what the migration experience is like.

Preparing My App for Swift 6
Photo by Dawid Zawiła / Unsplash

What is "Swift 6 mode"?

Swift 6 won't be released in 2023 anymore, that has been made clear by Doug Gregor from the Swift Language Workgroup. But did you know that Apple has already shipped parts of Swift 6 in 5.8? Yes, it's true. Some parts of Swift that shipped with Xcode 14.3 a few weeks ago are turned off by default. They will be turned on once Swift 6 comes out, which might take another year, or even longer.

These features introduce some breaking changes to Swift, for example by renaming known APIs, adjusting their behavior, or adding new safety checks to the compiler. But they will all get turned on at some point to help improve our code bases. And we will have to update our projects to play nicely with those changes.

I figured it's a good idea to turn on all features in my projects to see if there's any breaking change for my codebase that I should know of. And of course, I could also make use of some new features, such as BareSlashRegexLiterals, which makes the /.../ regex literal syntax available for concise Regex initialization.

Thankfully, with Swift 5.8 there's now a unified way of enabling these options: We just need to pass -enable-upcoming-feature to Swift by providing it in the OTHER_SWIFT_FLAGS in our projects build settings in Xcode. But we need to know also what features are available, and I couldn't find any place with a good overview (yet). The proposal that introduced this unified option does contain such a list, but it doesn't get updated with newer options added later on, such as the one from SE-0384. So, where can we currently reliably get a list of all supported options?

UPDATE: You can now filter by upcoming flags directly on Swift.org.

Swift is open source, so the most reliable place seems the Swift GitHub repository! It includes a Features.def file which contains entries (link to release/5.8 branch) named UPCOMING_FEATURE including both the related Swift Evolution proposal number and the Swift version they will be turned on:

UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
UPCOMING_FEATURE(ForwardTrailingClosures, 286, 6)
UPCOMING_FEATURE(BareSlashRegexLiterals, 354, 6)
UPCOMING_FEATURE(ExistentialAny, 335, 6)

Here are the options with a short description of what they do:

For some reason, two options seem not to be listed there (I'm investigating):

More options will ship with later versions, e.g. ImportObjcForwardDeclarations.

To additionally check my code for proper concurrency support, I chose to also pass -warn-concurrency (this should actually be the same as StrictConcurrency if that actually works) and -enable-actor-data-race-checks.


Want to see your ad here? Contact me at ads@fline.dev to get in touch.

Migrating my project

If you also want to enable all 5.8 options in your project, like me, copy (C) the following text block, then head to your Xcode projects "Build Settings" tab, search for "Other Swift Flags", select that option in the editor and paste (V) it in:

//:configuration = Debug
OTHER_SWIFT_FLAGS = -enable-upcoming-feature BareSlashRegexLiterals -enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ExistentialAny -enable-upcoming-feature ForwardTrailingClosures -enable-upcoming-feature ImplicitOpenExistentials -enable-upcoming-feature StrictConcurrency -warn-concurrency -enable-actor-data-race-checks

//:configuration = Release
OTHER_SWIFT_FLAGS = -enable-upcoming-feature BareSlashRegexLiterals -enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ExistentialAny -enable-upcoming-feature ForwardTrailingClosures -enable-upcoming-feature ImplicitOpenExistentials -enable-upcoming-feature StrictConcurrency -warn-concurrency -enable-actor-data-race-checks

//:completeSettings = some
OTHER_SWIFT_FLAGS

If you are using a SwiftPM-modularized app like me, or if you're working on a Swift package, you will need to additionally pass an array of .enableUpcomingFeature's to each target via swiftSettings:

let swiftSettings: [SwiftSetting] = [
   .enableUpcomingFeature("BareSlashRegexLiterals"),
   .enableUpcomingFeature("ConciseMagicFile"),
   .enableUpcomingFeature("ExistentialAny"),
   .enableUpcomingFeature("ForwardTrailingClosures"),
   .enableUpcomingFeature("ImplicitOpenExistentials"),
   .enableUpcomingFeature("StrictConcurrency"),
   .unsafeFlags(["-warn-concurrency", "-enable-actor-data-race-checks"]),
]

let package = Package(
   // ...
   targets: [
      // ...
      .target(
         name: "MyTarget",
         dependencies: [/* ... */],
         swiftSettings: swiftSettings
      ),
      // ...
   ]

Don't forget to upgrade your tools version at the top of the file to 5.8:

// swift-tools-version:5.8
.enableUpcomingFeature is only available from Swift 5.8

That's because Swift doesn't pass along the options you specified for your project target automatically to any modules you import into your project. And that's good news, because that way Swift packages you might be including into your project don't need any adjustments and you can still use these features for your own app code. And vice versa: Package authors can turn these features on for their projects without affecting the code in projects they get consumed in.

After turning these on and building, I ran into four kinds of issues:

1. I had to add the any keyword in several places – Xcode helped with a Fix-It:

2. For some reason I received a lot of errors for views with a .sheet modifier. The error pairs stated: "Generic parameter 'Content' could not be inferred" and "Missing argument for parameter 'content' in call". These messages weren't very helpful though, so I first tried to replace the .sheet content with just a Text view, but it didn't help. So I've tried turning off the options one by one and the errors disappeared when I turned off ForwardTrailingClosures, so I kept it turned off. I'm hoping that a better error message will be produced by future versions of Swift to fix them later. There's no hurry, after all. I can retry on Swift 5.9. I didn't have time to investigate now.

3. I had to mark some of my functions returning a TCA WithViewStore with the @MainActor attribute, but again, Xcode helped with Fix-Its.

4. In many places, warnings were shown stating: "Non-sendable type '...' passed in call to main actor-isolated function cannot cross actor boundary". So I made these types conform to the Sendable protocol (learn about Sendable here).

All else seemed to build fine for my app RemafoX with ~35k lines of Swift code. The whole process took less than 3 hours of my time.

My project is now ready for the future of Swift 🎉 and I can use the new Regex literal feature (let regex = /.*@.*/). Also, I can't introduce new code that I have to migrate to Swift 6 later, cause I will receive errors right away. 💯

How about your project? Which upcoming features do you want to migrate to?

💁🏻‍♂️
What is RemafoX?
A native Mac app that integrates with Xcode to help translate your app.
Get it now to save time during development & make localization easy.
👨🏻‍💻
Want to Connect?
Follow me on 👾 Twitch, 🎬 YouTube, 🐦 Twitter, and 🦣 Mastodon.