Swift was designed to be safe by default, preventing entire categories of programming mistakes at compile time. Sources of undefined behavior in C-based languages, such as using variables before they’re initialized or a use-after-free, are defined away in Swift.
An increasingly important source of undefined behavior is concurrent code that inadvertently accesses memory from one thread at the same time that another thread is writing to the same memory. This kind of unsafety is called a data race, and data races make concurrent programs exceptionally difficult to write correctly. Swift solves this problem through data isolation provided by actors and tasks, which guarantees mutually exclusive access to shared mutable state. Data isolation enforcement has been under active development since 2020 when the Swift concurrency roadmap was posted.
Swift 5.10 accomplishes full data isolation in the concurrency language model. This important milestone has taken years of active development over many releases. The concurrency model was introduced in Swift 5.5 including async
/await
, actors, and structured concurrency. Swift 5.7 introduced Sendable
as the fundamental concept for thread-safe types whose values can be shared across arbitrary concurrent contexts without introducing a risk of data races. And now, in Swift 5.10, full data isolation is enforced at compile time in all areas of the language when the complete concurrency checking option is enabled.
Full data isolation in Swift 5.10 sets the stage for the next major release, Swift 6. The Swift 6.0 compiler will offer a new, opt-in Swift 6 language mode that will enforce full data isolation by default, and we will embark upon the transition to eliminate data races across all software written in Swift.
Swift 5.10 will produce data-race warnings in some circumstances where the code could be proven safe with additional compiler analysis. A major focus of language development for the Swift 6 release is improving the usability of strict concurrency checking by mitigating false positive concurrency errors in common code patterns that are proven to be safe.
Read on to learn about full data isolation in Swift 5.10, new unsafe opt-outs for actor isolation checking, and the remaining concurrency evolution ahead of Swift 6.
Table of Contents
Data-race safety in Swift 5.10
Full data isolation
Swift 5.10 rounds out the data-race safety semantics in all corners of the language, and fixes numerous bugs in Sendable
and actor isolation checking to strengthen the guarantees of complete concurrency checking. When building code with the compiler flag -strict-concurrency=complete
, Swift 5.10 will diagnose the potential for data races at compile time except where an explicit unsafe opt-out, such as nonisolated(unsafe)
or @unchecked Sendable
, is used.
For example, in Swift 5.9, the following code fails an isolation assertion at runtime due to a @MainActor
-isolated initializer being evaluated outside the actor, but it was not diagnosed under -strict-concurrency=complete
:
@MainActor
class MyModel {
private init() {
MainActor.assertIsolated()
}
static let shared = MyModel()
}
func useShared() async {
let model = MyModel.shared
}
await useShared()
The above code admits data races. MyModel.shared
is a @MainActor
-isolated static variable, which evaluates a @MainActor
-isolated initial value upon first access. MyModel.shared
is accessed synchronously from a nonisolated
context inside the useShared()
function, so the initial value is computed off the main actor. In Swift 5.10, compiling the code with -strict-concurrency=complete
produces a warning that the access must be done asynchronously:
warning: expression is 'async' but is not marked with 'await'
let model = MyModel.shared
^~~~~~~~~~~~~~
await
The possible fixes for resolving the data race are 1) access MyModel.shared
asynchronously using await
, 2) make MyModel.init
and MyModel.shared
both nonisolated
and move the code that requires the main actor into a separate isolated method, or 3) isolate useShared()
to @MainActor
.
You can find more details about the changes and additions to the full data isolation programming model in the Swift 5.10 release notes.
Unsafe opt-outs
Unsafe opt-outs, such as @unchecked Sendable
conformances, are important for communicating that code is safe from data-races when it cannot be proven automatically by the compiler. These tools are necessary in cases where synchronization is implemented in a way that the compiler cannot reason about, such as through OS-specific primitives or when working with thread-safe types implemented in C/C++/Objective-C. However, @unchecked Sendable
conformances are difficult to use correctly, because they opt the entire type out of data-race safety checking. In many cases, only one specific property in a type needs the opt-out, while the rest of the implementation adheres to static concurrency safety.
Swift 5.10 introduces a new nonisolated(unsafe)
keyword to opt out of actor isolation checking for stored properties and variables. nonisolated(unsafe)
can be used on any form of storage, including stored properties, local variables, and global/static variables.
For example, global and static variables can be accessed from anywhere in your code, so they are required to either be immutable and Sendable
, or isolated to a global actor:
import Dispatch
struct MyData {
static let cacheQueue = DispatchQueue(...)
// All access to 'globalCache' is guarded by 'cacheQueue'
static var globalCache: [MyData] = []
}
When building the above code with -strict-concurrency=complete
, the compiler emits a warning:
warning: static property 'globalCache' is not concurrency-safe because it is non-isolated global shared mutable state
static var globalCache: [MyData] = []
^
note: isolate 'globalCache' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'
All uses of globalCache
are guarded by cacheQueue.async { ... }
, so this code is free of data races in practice. In this case, nonisolated(unsafe)
can be applied to the static variable to silence the concurrency warning:
import Dispatch
struct MyData {
static let cacheQueue = DispatchQueue(...)
// All access to 'globalCache' is guarded by 'cacheQueue'
nonisolated(unsafe) static var globalCache: [MyData] = []
}
nonisolated(unsafe)
also eliminates the need for @unchecked Sendable
wrapper types that are used only to pass specific instances of non-Sendable
values across isolation boundaries when there is no potential for concurrent access:
// 'MutableData' is not 'Sendable'
class MutableData { ... }
func processData(_: MutableData) async { ... }
@MainActor func send() async {
nonisolated(unsafe) let data = MutableData()
await processData(data)
}
Note that without correct implementation of a synchronization mechanism to achieve data isolation, dynamic analysis from exclusivity enforcement or tools such as the Thread Sanitizer may still identify failures.
Language evolution ahead of Swift 6
The next release of Swift will be Swift 6. The complete concurrency model in Swift 5.10 is overly restrictive, and several Swift Evolution proposals are in active development to improve the usability of full data isolation by removing false postive data-race errors. This work includes lifting limitations on passing non-Sendable
values across isolation boundaries when the compiler determines there is no potential for concurrent access, more effective Sendable
inference for functions and key-paths, and more. You can find the set of proposals that will round out Swift 6 at Swift.org/swift-evolution.
Next Steps
Try out complete concurrency checking
You can help shape the transition to the Swift 6 language mode by trying out complete concurrency checking in your project and providing feedback on your experience.
If you find any remaining compiler bugs where complete concurrency checking does not diagnose a data race at compile time, please report an issue.
You can also provide feedback that helps improve the concurrency documentation, compiler error messages, and the upcoming Swift 6 migration guide. If you encounter a case where the compiler diagnoses a data-race warning that you don’t understand or you’re not sure how to resolve a given data-race warning, please start a discussion thread on the Swift forums using the concurrency
tag.
Downloads
Official binaries for Swift 5.10 are available for download from Swift.org for macOS, Windows, and Linux.
Swift Evolution Appendix
The following language proposals were accepted through the Swift Evolution process and implemented in Swift 5.10:
Leave a Reply