Is 2025 the year to fully adopt Swift 6?

Published on: January 9, 2025

When Apple released Xcode 16 last year, they made the Swift 6 compiler available along with it. This means that we can create new projects using Swift 6 and its compile-time data race protections.

However, the big question for many developers is: Is 2025 the right time to adopt Swift 6 fully, or should we stick with Swift 5 for now?

In this post, I won’t give you a definitive answer. Instead, I’ll share my perspective and reasoning to help you decide whether adopting Swift 6 is right for you and your project(s).

The right answer depends on loads of variables like the project you work on, the team you work with, and your knowledge of Swift Concurrency.

Xcode 16, existing projects, and Swift 6

If you’ve opened an existing project in Xcode 16, you might not have noticed any immediate changes. While the Swift 6 compiler is used in Xcode 16 for all projects, Xcode defaults to the Swift 5 language mode for existing projects.

If you’ve experienced previous major migrations in Swift, you’ll remember that Xcode would usually prompt you to make changes to your project in order to make sure your project still works. This happened for the migration from Swift 1.2 to Swift 2, and from Swift 2 to Swift 3.

We got a new compiler, and we were forced to adopt the new Swift language version that came along with it.

Since then, the compiler has gained some “language modes”, and the Swift 6 compiler comes with a Swift 5 language mode.

The Swift 5 language mode allows the Swift 6 compiler to function without enforcing all the stricter rules of Swift 6. For example, the Swift 5 language mode will make it so that compile-time data race protections are not turned on.

So, when we talk about adopting Swift 6, we’re really talking about opting into the Swift 6 language mode.

Existing projects that are opened in Xcode 16 will, automatically, use the Swift 5 language mode. That’s why your project still compiles perfectly fine without adopting Swift 6.

What about new projects?

New projects in Xcode 16 also default to Swift 5 language mode. However, Swift packages created with the Swift 6 toolchain default to Swift 6 language mode unless explicitly configured otherwise. This distinction is important, because when you create new packages you’re operating in a different language mode than project (and that’s perfectly fine).

If you’re interested in enabling the Swift 6 language mode for existing projects or packages, I have some blog posts about that here:

Challenges of Adopting Swift 6

Switching to Swift 6 language mode can make projects that compiled just fine with Swift 5 break completely. For example, you’ll run into errors about capturing non-sendable parameters, sendable closures, and actor isolation.

Some fixes are straightforward—like making an immutable object explicitly sendable or refactoring objects that are used in async functions into actors. However, other issues, especially those involving crossing isolation boundaries, can be much trickier to fix.

For example, adding actors to resolve sendability errors often requires refactoring synchronous code into asynchronous code, leading to a ripple effect throughout your codebase. Even seemingly simple interactions with an actor require await, even for non-async functions because actors operate in their own isolation contexts.

Adopting actors is typically a task that will take much, much longer than you might expect initially.

Resolving errors with @MainActor

A common workaround is to liberally apply @MainActor annotations. While this reduces concurrency-related errors by forcing most code to run on the main thread, it’s not always the solution that you’re looking for. While not inherently wrong, this approach should be used with caution.

Reducing crossing of isolation boundaries

Apple recognizes the challenges of adopting Swift 6, especially for existing projects. One significant aspect of Swift Concurrency that can make adoption tricky is how non-isolated asynchronous functions inherit isolation contexts. Currently, nonisolated async functions run on a background thread unless explicitly isolated, which can lead to unnecessary crossing of isolation boundaries.

Apple is exploring ways for such functions to inherit the caller’s isolation context, potentially reducing sendability errors and making adoption of Swift 6 much more straightforward.

So, should we adopt Swift 6?

For existing projects, I recommend proceeding cautiously. Stick with Swift 5 language mode unless:

• Your project is small and manageable for migration.

• You have a strong understanding of concurrency concepts and can commit to resolving sendability issues.

New projects can be built with Swift 6 language mode from the start, but be prepared for challenges, especially when interacting with Apple’s frameworks, which may lack full concurrency support.

If you’re modularizing your codebase with Swift packages, I recommend using Swift 6 language mode for your (new) packages, as packages generally have fewer dependencies on Apple’s frameworks and are easier to adapt and you can have Swift 5 and Swift 6 modules in the same project.

Getting ready to adopt Swift 6

Before adopting Swift 6, ensure you understand:

• Sendability and how to resolve related errors.

• The use of actors and their impact on isolation and asynchronicity.

• How to navigate ambiguous compiler errors.

I cover all of these topics and more in my book, Practical Swift Concurrency as well as my workshops. You can also review and study Swift evolution proposals and forum discussions to get a good sense of how Swift Concurrency works.

If you’ve started adopting Swift 6 or decided to hold off, I’d love to hear your experiences! Connect with me on X, BlueSky, or Mastodon.

Subscribe to my newsletter