Skip to content

Commit d0244d7

Browse files
authored
Merge pull request #71085 from hborla/5.10-data-race-safety-changelog
[5.10][Release Notes] Swift 5.10 closes all known holes in compile-time strict concurrency checking.
2 parents 99e9db8 + 383761b commit d0244d7

File tree

1 file changed

+109
-19
lines changed

1 file changed

+109
-19
lines changed

CHANGELOG.md

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,142 @@
55
66
## Swift 5.10
77

8+
* Swift 5.10 closes all known static data-race safey holes in complete strict
9+
concurrency checking.
10+
11+
When writing code against `-strict-concurrency=complete`, Swift 5.10 will
12+
diagnose all potential for data races at compile time unless an explicit
13+
unsafe opt out, such as `nonisolated(unsafe)` or `@unchecked Sendable`, is
14+
used.
15+
16+
For example, in Swift 5.9, the following code crashes at runtime due to a
17+
`@MainActor`-isolated initializer being evaluated outside the actor, but it
18+
was not diagnosed under `-strict-concurrency=complete`:
19+
20+
```swift
21+
@MainActor
22+
class MyModel {
23+
init() {
24+
MainActor.assertIsolated()
25+
}
26+
27+
static let shared = MyModel()
28+
}
29+
30+
func useShared() async {
31+
let model = MyModel.shared
32+
}
33+
34+
await useShared()
35+
```
36+
37+
The above code admits data races because a `@MainActor`-isolated static
38+
variable, which evaluates a `@MainActor`-isolated initial value upon first
39+
access, is accessed synchronously from a `nonisolated` context. In Swift
40+
5.10, compiling the code with `-strict-concurrency=complete` produces a
41+
warning that the access must be done asynchronously:
42+
43+
```
44+
warning: expression is 'async' but is not marked with 'await'
45+
let model = MyModel.shared
46+
^~~~~~~~~~~~~~
47+
await
48+
```
49+
50+
Swift 5.10 fixed numerous other bugs in `Sendable` and actor isolation
51+
checking to strengthen the guarantees of complete concurrency checking.
52+
53+
Note that the complete concurrency model in Swift 5.10 is conservative.
54+
Several Swift Evolution proposals are in active development to improve the
55+
usability of strict concurrency checking ahead of Swift 6.
56+
857
* [SE-0412][]:
958

10-
Under strict concurrency checking, every global or static variable must be either isolated to a global actor or be both immutable and of `Sendable` type.
59+
Global and static variables are prone to data races because they provide memory that can be accessed from any program context. Strict concurrency checking in Swift 5.10 prevents data races on global and static variables by requiring them to be either:
60+
61+
1. isolated to a global actor, or
62+
2. immutable and of `Sendable` type.
63+
64+
For example:
1165

1266
```swift
1367
var mutableGlobal = 1
1468
// warning: var 'mutableGlobal' is not concurrency-safe because it is non-isolated global shared mutable state
1569
// (unless it is top-level code which implicitly isolates to @MainActor)
1670

17-
final class NonsendableType {
18-
init() {}
71+
@MainActor func mutateGlobalFromMain() {
72+
mutableGlobal += 1
73+
}
74+
75+
nonisolated func mutateGlobalFromNonisolated() async {
76+
mutableGlobal += 10
1977
}
2078

2179
struct S {
22-
static let immutableNonsendable = NonsendableType()
23-
// warning: static property 'immutableNonsendable' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor
80+
static let immutableSendable = 10
81+
// okay; 'immutableSendable' is safe to access concurrently because it's immutable and 'Int' is 'Sendable'
2482
}
2583
```
2684

27-
The attribute `nonisolated(unsafe)` can be used to annotate a global variable (or any form of storage) to disable static checking of data isolation, but note that without correct implementation of a synchronization mechanism to achieve data isolation, dynamic run-time analysis from exclusivity enforcement or tools such as Thread Sanitizer could still identify failures.
85+
A new `nonisolated(unsafe)` modifier can be used to annotate a global or static variable to suppress data isolation violations when manual synchronization is provided:
2886

2987
```swift
30-
nonisolated(unsafe) var global: String
88+
// This global is only set in one part of the program
89+
nonisolated(unsafe) var global: String!
3190
```
3291

92+
`nonisolated(unsafe)` can be used on any form of storage, including stored properties and local variables, as a more granular opt out for `Sendable` checking, eliminating the need for `@unchecked Sendable` wrapper types in many use cases:
93+
94+
```swift
95+
import Dispatch
96+
97+
// 'MutableData' is not 'Sendable'
98+
class MutableData { ... }
99+
100+
final class MyModel: Sendable {
101+
private let queue = DispatchQueue(...)
102+
// 'protectedState' is manually isolated by 'queue'
103+
nonisolated(unsafe) private var protectedState: MutableData
104+
}
105+
```
106+
107+
Note that without correct implementation of a synchronization mechanism to achieve data isolation, dynamic run-time analysis from exclusivity enforcement or tools such as the Thread Sanitizer could still identify failures.
108+
33109
* [SE-0411][]:
34110

35-
Default value expressions can now have the same isolation as the enclosing
36-
function or the corresponding stored property:
111+
Swift 5.10 closes a data-race safety hole that previously permitted isolated
112+
default stored property values to be synchronously evaluated from outside the
113+
actor. For example, the following code compiles warning-free under
114+
`-strict-concurrency=complete` in Swift 5.9, but it will crash at runtime at
115+
the call to `MainActor.assertIsolated()`:
37116

38117
```swift
39-
@MainActor
40-
func requiresMainActor() -> Int { ... }
118+
@MainActor func requiresMainActor() -> Int {
119+
MainActor.assertIsolated()
120+
return 0
121+
}
41122

42-
class C {
43-
@MainActor
44-
var x: Int = requiresMainActor()
123+
@MainActor struct S {
124+
var x = requiresMainActor()
125+
var y: Int
126+
}
127+
128+
nonisolated func call() async {
129+
let s = await S(y: 10)
45130
}
46131

47-
@MainActor func defaultArg(value: Int = requiresMainActor()) { ... }
132+
await call()
48133
```
49134

50-
For isolated default values of stored properties, the implicit initialization
51-
only happens in the body of an `init` with the same isolation. This closes
52-
an important data-race safety hole where global-actor-isolated default values
53-
could inadvertently run synchronously from outside the actor.
135+
This happens because `requiresMainActor()` is used as a default argument to
136+
the member-wise initializer of `S`, but default arguments are always
137+
evaluated in the caller. In this case, the caller runs on the generic
138+
executor, so the default argument evaluation crashes.
139+
140+
Under `-strict-concurrency=complete` in Swift 5.10, default argument values
141+
can safely share the same isolation as the enclosing function or stored
142+
property. The above code is still valid, but the isolated default argument is
143+
guaranteed to be evaluated in the callee's isolation domain.
54144

55145
## Swift 5.9.2
56146

0 commit comments

Comments
 (0)