|
5 | 5 |
|
6 | 6 | ## Swift 6.0
|
7 | 7 |
|
| 8 | +* Swift 6 comes with a new language mode that prevents the risk of data races |
| 9 | + at compile time. This guarantee is accomplished through _data isolation_; the |
| 10 | + compiler will validate that data passed over a boundary between concurrently |
| 11 | + executing code is either safe to reference concurrently, or mutually |
| 12 | + exclusive access to the value is enforced. |
| 13 | + |
| 14 | + The data-race safety checks were previously available in Swift 5.10 through |
| 15 | + the `-strict-concurrency=complete` compiler flag. Complete concurrency |
| 16 | + checking in Swift 5.10 was overly restrictive, and Swift 6 removes many |
| 17 | + false-positive data-race warnings through better `Sendable` inference, |
| 18 | + new analysis that proves mutually exclusive access when passing values with |
| 19 | + non-`Sendable` type over isolation boundaries, and more. |
| 20 | + |
| 21 | + You can enable the Swift 6 language mode using the `-swift-version 6` |
| 22 | + compiler flag. |
| 23 | + |
| 24 | +* [SE-0428][]: |
| 25 | + Distributed actors now have the ability to support complete split server / |
| 26 | + client systems, thanks to the new `@Resolvable` macro and runtime changes. |
| 27 | + |
| 28 | + It is now possible to share an "API module" between a client and server |
| 29 | + application, declare a resolvable distributed actor protocol with the expected |
| 30 | + API contract and perform calls on it, without knowing the specific type the |
| 31 | + server is implementing those actors as. |
| 32 | + |
| 33 | + Declaring such protocol looks like this: |
| 34 | + |
| 35 | +```swift |
| 36 | +import Distributed |
| 37 | + |
| 38 | +@Resolvable |
| 39 | +protocol Greeter where ActorSystem: DistributedActorSystem<any Codable> { |
| 40 | + distributed func greet(name: String) -> String |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +And the module structure to support such applications looks like this: |
| 45 | + |
| 46 | +``` |
| 47 | + ┌────────────────────────────────────────┐ |
| 48 | + │ API Module │ |
| 49 | + │========================================│ |
| 50 | + │ @Resolvable │ |
| 51 | + │ protocol Greeter: DistributedActor { │ |
| 52 | + ┌───────┤ distributed func greet(name: String) ├───────┐ |
| 53 | + │ │ } │ │ |
| 54 | + │ └────────────────────────────────────────┘ │ |
| 55 | + │ │ |
| 56 | + ▼ ▼ |
| 57 | +┌────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────┐ |
| 58 | +│ Client Module │ │ Server Module │ |
| 59 | +│================================================│ │==============================================│ |
| 60 | +│ let g = try $Greeter.resolve(...) /*new*/ │ │ distributed actor EnglishGreeter: Greeter { │ |
| 61 | +│ try await greeter.hello(name: ...) │ │ distributed func greet(name: String) { │ |
| 62 | +└────────────────────────────────────────────────┘ │ "Greeting in english, for \(name)!" │ |
| 63 | +/* Client cannot know about EnglishGreeter type */ │ } │ |
| 64 | + │ } │ |
| 65 | + └──────────────────────────────────────────────┘ |
| 66 | +``` |
| 67 | + |
| 68 | +* [SE-0424][]: |
| 69 | + Serial executor gains a new customization point `checkIsolation()`, which can be |
| 70 | + implemented by custom executor implementations in order to provide a last resort |
| 71 | + check before the isolation asserting APIs such as `Actor.assumeIsolated` or |
| 72 | + `assertIsolated` fail and crash. |
| 73 | + |
| 74 | + This specifically enables Dispatch to implement more sophisticated isolation |
| 75 | + checking, and now even an actor which is "on a queue which is targeting |
| 76 | + another specific queue" can be properly detected using these APIs. |
| 77 | + |
| 78 | +* Closures can now appear in pack expansion expressions, which allows you to |
| 79 | + construct a parameter pack of closures where each closure captures the |
| 80 | + corresponding element of some other parameter pack. For example: |
| 81 | + |
| 82 | + ```swift |
| 83 | + struct Manager<each T> { |
| 84 | + let fn: (repeat () -> (each T)) |
| 85 | + |
| 86 | + init(_ t: repeat each T) { |
| 87 | + fn = (repeat { each t }) |
| 88 | + } |
| 89 | + } |
| 90 | + ``` |
| 91 | + |
| 92 | +* [SE-0431][]: |
| 93 | + You can now require a function value to carry its actor isolation |
| 94 | + dynamically in a way that can be directly read by clients: |
| 95 | + |
| 96 | + ```swift |
| 97 | + func apply<R>(count: Int, |
| 98 | + operation: @isolated(any) async () -> R) async -> [R] |
| 99 | + where R: Sendable { |
| 100 | + // implementation |
| 101 | + } |
| 102 | + ``` |
| 103 | + |
| 104 | + The isolation can read with the `.isolation` property, which has type |
| 105 | + `(any Actor)?`: |
| 106 | + |
| 107 | + ```swift |
| 108 | + let iso = operation.isolation |
| 109 | + ``` |
| 110 | + |
| 111 | + This capability has been adopted by the task-creation APIs in the |
| 112 | + standard library. As a result, creating a task with an actor-isolated |
| 113 | + function will now synchronously enqueue the task on the actor, which |
| 114 | + can be used for transitive event-ordering guarantees if the actor |
| 115 | + guarantees that jobs will be run in the order they are enqueued, as |
| 116 | + `@MainActor` does. If the function is not explicitly isolated, Swift |
| 117 | + still retains the right to optimize enqueues for functions that actually |
| 118 | + start by doing work with different isolation from their formal isolation. |
| 119 | + |
| 120 | +* [SE-0423][]: |
| 121 | + You can now use `@preconcurrency` attribute to replace static actor isolation |
| 122 | + checking with dynamic checks for witnesses of synchronous nonisolated protocol |
| 123 | + requirements when the witness is isolated. This is common when Swift programs |
| 124 | + need to interoperate with frameworks written in C/C++/Objective-C whose |
| 125 | + implementations cannot participate in static data race safety. |
| 126 | + |
| 127 | + ```swift |
| 128 | + public protocol ViewDelegateProtocol { |
| 129 | + func respondToUIEvent() |
| 130 | + } |
| 131 | + ``` |
| 132 | + |
| 133 | + It's now possible for a `@MainActor`-isolated type to conform to |
| 134 | + `ViewDelegateProtocol` by marking conformance declaration as `@preconcurrency`: |
| 135 | + |
| 136 | + ```swift |
| 137 | + @MainActor |
| 138 | + class MyViewController: @preconcurrency ViewDelegateProtocol { |
| 139 | + func respondToUIEvent() { |
| 140 | + // implementation... |
| 141 | + } |
| 142 | + } |
| 143 | + ``` |
| 144 | + |
| 145 | + The compiler would emit dynamic checks into the `respondToUIEvent()` witness |
| 146 | + to make sure that it's always executed in `@MainActor` isolated context. |
| 147 | + |
| 148 | + Additionally, the compiler would emit dynamic actor isolation checks for: |
| 149 | + |
| 150 | + - `@objc` thunks of synchronous actor-isolated members of classes. |
| 151 | + |
| 152 | + - Synchronous actor-isolated function values passed to APIs that |
| 153 | + erase actor isolation and haven't yet adopted strict concurrency checking. |
| 154 | + |
| 155 | + - Call-sites of synchronous actor-isolated functions imported from Swift 6 libraries. |
| 156 | + |
| 157 | + The dynamic actor isolation checks can be disabled using the flag |
| 158 | + `-disable-dynamic-actor-isolation`. |
| 159 | + |
| 160 | +* [SE-0420][]: |
| 161 | + `async` functions can now explicitly inherit the isolation of their caller |
| 162 | + by declaring an `isolated` parameter with the default value of `#isolation`: |
| 163 | + |
| 164 | + ```swift |
| 165 | + func poll(isolation: isolated (any Actor)? = #isolation) async -> [Item] { |
| 166 | + // implementation |
| 167 | + } |
| 168 | + ``` |
| 169 | + |
| 170 | + When the caller is actor-isolated, this allows it to pass isolated state |
| 171 | + to the function, which would otherwise have concurrency problems. The |
| 172 | + function may also be able to eliminate unwanted scheduling changes, such |
| 173 | + as when it can quickly return in a fast path without needing to suspend. |
| 174 | + |
| 175 | +* [SE-0418][]: |
| 176 | + |
| 177 | + The compiler would now automatically employ `Sendable` on functions |
| 178 | + and key path literal expressions that cannot capture non-Sendable values. |
| 179 | + |
| 180 | + This includes partially-applied and unapplied instance methods of `Sendable` |
| 181 | + types, as well as non-local functions. Additionally, it is now disallowed |
| 182 | + to utilize `@Sendable` on instance methods of non-Sendable types. |
| 183 | + |
| 184 | + Let's use the following type to illustrate the new inference rules: |
| 185 | + |
| 186 | + ```swift |
| 187 | + public struct User { |
| 188 | + var name: String |
| 189 | + |
| 190 | + func getAge() -> Int { ... } |
| 191 | + } |
| 192 | + ``` |
| 193 | + |
| 194 | + Key path `\User.name` would be inferred as `WritableKeyPath<User, String> & Sendable` |
| 195 | + because it doesn't capture any non-Sendable values. |
| 196 | + |
| 197 | + The same applies to keypath-as-function conversions: |
| 198 | + |
| 199 | + ```swift |
| 200 | + let _: @Sendable (User) -> String = \User.name // Ok |
| 201 | + ``` |
| 202 | + |
| 203 | + A function value produced by an un-applied reference to `getAge` |
| 204 | + would be marked as `@Sendable` because `User` is a `Sendable` struct: |
| 205 | + |
| 206 | + ```swift |
| 207 | + let _ = User.getAge // Inferred as `@Sendable (User) -> @Sendable () -> Int` |
| 208 | + |
| 209 | + let user = User(...) |
| 210 | + user.getAge // Inferred as `@Sendable () -> Int` |
| 211 | + ``` |
| 212 | + |
| 213 | +* [SE-0432][]: |
| 214 | + Noncopyable enums can be pattern-matched with switches without consuming the |
| 215 | + value you switch over: |
| 216 | + |
| 217 | + ```swift |
| 218 | + enum Lunch: ~Copyable { |
| 219 | + case soup |
| 220 | + case salad |
| 221 | + case sandwich |
| 222 | + } |
| 223 | + |
| 224 | + func isSoup(_ lunch: borrowing Lunch) -> Bool { |
| 225 | + switch lunch { |
| 226 | + case .soup: true |
| 227 | + default: false |
| 228 | + } |
| 229 | + } |
| 230 | + ``` |
| 231 | + |
| 232 | + |
| 233 | +* [SE-0429][]: |
| 234 | + The noncopyable fields of certain types can now be consumed individually: |
| 235 | + |
| 236 | + ```swift |
| 237 | + struct Token: ~Copyable {} |
| 238 | + |
| 239 | + struct Authentication: ~Copyable { |
| 240 | + let id: Token |
| 241 | + let name: String |
| 242 | + |
| 243 | + mutating func exchange(_ new: consuming Token) -> Token { |
| 244 | + let old = self.id // <- partial consumption of 'self' |
| 245 | + self = .init(id: new, name: self.name) |
| 246 | + return old |
| 247 | + } |
| 248 | + } |
| 249 | + ``` |
| 250 | + |
| 251 | +* [SE-0430][]: |
| 252 | + |
| 253 | + Region Based Isolation is now extended to enable the application of an |
| 254 | + explicit `sending` annotation to function parameters and results. A function |
| 255 | + parameter or result that is annotated with `sending` is required to be |
| 256 | + disconnected at the function boundary and thus possesses the capability of |
| 257 | + being safely sent across an isolation domain or merged into an actor-isolated |
| 258 | + region in the function's body or the function's caller respectively. Example: |
| 259 | + |
| 260 | + ```swift |
| 261 | + func parameterWithoutSending(_ x: NonSendableType) async { |
| 262 | + // Error! Cannot send a task-isolated value to the main actor! |
| 263 | + await transferToMainActor(x) |
| 264 | + } |
| 265 | + |
| 266 | + func parameterWithSending(_ x: sending NonSendableType) async { |
| 267 | + // Ok since `x` is `sending` and thus disconnected. |
| 268 | + await transferToMainActor(x) |
| 269 | + } |
| 270 | + ``` |
| 271 | + |
| 272 | +* [SE-0414][]: |
| 273 | + |
| 274 | + The compiler is now capable of determining whether or not a value that does |
| 275 | + not conform to the `Sendable` protocol can safely be sent over an isolation |
| 276 | + boundary. This is done by introducing the concept of *isolation regions* that |
| 277 | + allows the compiler to reason conservatively if two values can affect each |
| 278 | + other. Through the usage of isolation regions, the compiler can now prove that |
| 279 | + sending a value that does not conform to the `Sendable` protocol over an |
| 280 | + isolation boundary cannot result in races because the value (and any other |
| 281 | + value that might reference it) is not used in the caller after the point of |
| 282 | + sending allowing code like the following to compile: |
| 283 | + |
| 284 | + ```swift |
| 285 | + actor MyActor { |
| 286 | + init(_ x: NonSendableType) { ... } |
| 287 | + } |
| 288 | + |
| 289 | + func useValue() { |
| 290 | + let x = NonSendableType() |
| 291 | + let a = await MyActor(x) // Error without Region Based Isolation! |
| 292 | + } |
| 293 | + ``` |
| 294 | + |
8 | 295 | * [SE-0427][]:
|
9 | 296 | You can now suppress `Copyable` on protocols, generic parameters,
|
10 | 297 | and existentials:
|
@@ -10282,11 +10569,23 @@ using the `.dynamicType` member to retrieve the type of an expression should mig
|
10282 | 10569 | [SE-0407]: https://github.com/apple/swift-evolution/blob/main/proposals/0407-member-macro-conformances.md
|
10283 | 10570 | [SE-0408]: https://github.com/apple/swift-evolution/blob/main/proposals/0408-pack-iteration.md
|
10284 | 10571 | [SE-0411]: https://github.com/apple/swift-evolution/blob/main/proposals/0411-isolated-default-values.md
|
10285 |
| -[SE-0417]: https://github.com/apple/swift-evolution/blob/main/proposals/0417-task-executor-preference.md |
10286 | 10572 | [SE-0412]: https://github.com/apple/swift-evolution/blob/main/proposals/0412-strict-concurrency-for-global-variables.md
|
10287 | 10573 | [SE-0413]: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md
|
| 10574 | +[SE-0414]: https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md |
| 10575 | +[SE-0417]: https://github.com/apple/swift-evolution/blob/main/proposals/0417-task-executor-preference.md |
| 10576 | +[SE-0418]: https://github.com/apple/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md |
| 10577 | +[SE-0420]: https://github.com/apple/swift-evolution/blob/main/proposals/0420-inheritance-of-actor-isolation.md |
10288 | 10578 | [SE-0422]: https://github.com/apple/swift-evolution/blob/main/proposals/0422-caller-side-default-argument-macro-expression.md
|
| 10579 | +[SE-0423]: https://github.com/apple/swift-evolution/blob/main/proposals/0423-dynamic-actor-isolation.md |
10289 | 10580 | [SE-0427]: https://github.com/apple/swift-evolution/blob/main/proposals/0427-noncopyable-generics.md
|
| 10581 | +[SE-0429]: https://github.com/apple/swift-evolution/blob/main/proposals/0429-partial-consumption.md |
| 10582 | +[SE-0432]: https://github.com/apple/swift-evolution/blob/main/proposals/0432-noncopyable-switch.md |
| 10583 | +[SE-0430]: https://github.com/apple/swift-evolution/blob/main/proposals/0430-transferring-parameters-and-results.md |
| 10584 | +[SE-0418]: https://github.com/apple/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md |
| 10585 | +[SE-0423]: https://github.com/apple/swift-evolution/blob/main/proposals/0423-dynamic-actor-isolation.md |
| 10586 | +[SE-0424]: https://github.com/apple/swift-evolution/blob/main/proposals/0424-custom-isolation-checking-for-serialexecutor.md |
| 10587 | +[SE-0428]: https://github.com/apple/swift-evolution/blob/main/proposals/0428-resolve-distributed-actor-protocols.md |
| 10588 | +[SE-0431]: https://github.com/apple/swift-evolution/blob/main/proposals/0431-isolated-any-functions.md |
10290 | 10589 | [#64927]: <https://github.com/apple/swift/issues/64927>
|
10291 | 10590 | [#42697]: <https://github.com/apple/swift/issues/42697>
|
10292 | 10591 | [#42728]: <https://github.com/apple/swift/issues/42728>
|
|
0 commit comments