Skip to content

Swift 5.10 release blog post #542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions _posts/2024-03-05-swift-5.10-released.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
layout: post
published: true
date: 2024-03-05 14:00:00
title: Swift 5.10 Released
author: [hborla]
---

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](https://forums.swift.org/t/swift-concurrency-roadmap/41611) 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.

## 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`:

```swift
@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](https://github.com/apple/swift/blob/release/5.10/CHANGELOG.md).

### 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:

```swift
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:

```swift
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:

```swift
// '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](https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md) when the compiler determines there is no potential for concurrent access, [more effective `Sendable` inference for functions and key-paths](https://github.com/apple/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md), and more. You can find the set of proposals that will round out Swift 6 at [Swift.org/swift-evolution](https://www.swift.org/swift-evolution/#?version=6.0).

## 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](/documentation/concurrency/) 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](https://github.com/apple/swift/issues/new/choose).

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](https://forums.swift.org/tags/c/swift-users/15/concurrency) using the `concurrency` tag.

### Downloads

Official binaries for Swift 5.10 are [available for download](https://swift.org/download/) from [Swift.org](http://swift.org/) for macOS, Windows, and Linux.

## Swift Evolution Appendix

The following language proposals were accepted through the [Swift Evolution](https://github.com/apple/swift-evolution) process and [implemented in Swift 5.10](https://www.swift.org/swift-evolution/#?version=5.10):

* SE-0327: [On Actors and Initialization](https://github.com/apple/swift-evolution/blob/main/proposals/0327-actor-initializers.md)
* SE-0383: [Deprecate @UIApplicationMain and @NSApplicationMain](https://github.com/apple/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md)
* SE-0404: [Allow Protocols to be Nested in Non-Generic Contexts](https://github.com/apple/swift-evolution/blob/main/proposals/0404-nested-protocols.md)
* SE-0411: [Isolated default value expressions](https://github.com/apple/swift-evolution/blob/main/proposals/0411-isolated-default-values.md)
* SE-0412: [Strict concurrency for global variables](https://github.com/apple/swift-evolution/blob/main/proposals/0412-strict-concurrency-for-global-variables.md)
64 changes: 64 additions & 0 deletions documentation/concurrency/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
layout: page
date: 2024-03-03 12:00:00
title: Enabling Complete Concurrency Checking
---

Data-race safety in the Swift 6 language mode is designed for incremental migration. You can address data-race safety issues in your projects module-by-module, and you can enable the compiler's actor isolation and `Sendable` checking as warnings in the Swift 5 language mode, allowing you to assess your progress toward eliminating data races before turning on the Swift 6 language mode.

Complete data-race safety checking can be enabled as warnings in the Swift 5 language mode using the `-strict-concurrency` compiler flag.

## Using the Swift compiler

To enable complete concurrency checking when running `swift` or `swiftc` directly at the command line, pass `-strict-concurrency=complete`:

```
~ swift -strict-concurrency=complete main.swift
```

## Using SwiftPM

### In a SwiftPM command-line invocation

`-strict-concurrency=complete` can be passed in a Swift package manager command-line invocation using the `-Xswiftc` flag:

```
~ swift build -Xswiftc -strict-concurrency=complete
~ swift test -Xswiftc -strict-concurrency=complete
```

This can be useful to gauge the amount of concurrency warnings before adding the flag permanently in the package manifest as described in the following section.

### In a SwiftPM package manifest

To enable complete concurrency checking for a target in a Swift package using Swift 5.9 or Swift 5.10 tools, use [`SwiftSetting.enableExperimentalFeature`](https://developer.apple.com/documentation/packagedescription/swiftsetting/enableexperimentalfeature(_:_:)) in the Swift settings for the given target:

```swift
.target(
name: "MyTarget",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
)
```

When using Swift 6.0 tools or later, use [`SwiftSetting.enableUpcomingFeature`](https://developer.apple.com/documentation/packagedescription/swiftsetting/enableupcomingfeature(_:_:)) in the Swift settings for the given target:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When using Swift 6.0 tools or later, use [`SwiftSetting.enableUpcomingFeature`](https://developer.apple.com/documentation/packagedescription/swiftsetting/enableupcomingfeature(_:_:)) in the Swift settings for the given target:
When using Swift 6.0 tools or later in Swift 5 language mode, use [`SwiftSetting.enableUpcomingFeature`](https://developer.apple.com/documentation/packagedescription/swiftsetting/enableupcomingfeature(_:_:)) in the Swift settings for the given target:

I believe it is the case that Swift 6.0 tools using Swift 6 languages mode will always have strict concurrency checking. So this flag would only be useful for Swift 5 language mode. I proposed that change to clarify for the reader.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll clarify this at the start of the document, because all of these instructions only apply when building with -swift-version 5.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I already tried to say this in the intro of the document

Data-race safety in the Swift 6 language mode is designed for incremental migration. You can address data-race safety issues in your projects module-by-module, and you can enable the compiler's actor isolation and Sendable checking as warnings in the Swift 5 language mode, allowing you to assess your progress toward eliminating data races before turning on the Swift 6 language mode.

Complete data-race safety checking can be enabled as warnings in the Swift 5 language mode using the -strict-concurrency compiler flag.

Do you think this is not sufficient? I understand that a lot of folks don't really understand the difference between Swift tools version vs Swift language version, but is this the right document to explain that difference?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it would hurt to include it here for clarity. I think sometimes people skim or skip the overview and skip down to the 'how to' instructions.

I agree this isn't the right place to explain the difference between compiler version and language mode, but I think mentioning it here lets readers know it exists.


```swift
.target(
name: "MyTarget",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency")
]
)
```

## Using Xcode

To enable complete concurrency checking in an Xcode project, set the "Strict Concurrency Checking" setting to "Complete" in the Xcode build settings. Alternatively, you can set `SWIFT_STRICT_CONCURRENCY` to `complete` in an xcconfig file:

```
// In a Settings.xcconfig

SWIFT_STRICT_CONCURRENCY = complete;
```