|
1 |
| -# `NonisolatedNonsendingByDefault` |
| 1 | +# nonisolated(nonsending) by Default (NonisolatedNonsendingByDefault) |
2 | 2 |
|
3 |
| -This feature changes the behavior of nonisolated async |
4 |
| -functions to run on the actor to which the caller is isolated (if any) by |
5 |
| -default, and provides an explicit way to express the execution semantics for |
6 |
| -these functions. |
| 3 | +Runs nonisolated async functions on the caller's actor by default. |
7 | 4 |
|
8 |
| -This feature was proposed in [SE-0461](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md) |
9 | 5 |
|
10 |
| -* The `@concurrent` attribute specifies that a function must always |
11 |
| - switch off of an actor to run. |
12 |
| - This is the default behavior without `NonisolatedNonsendingByDefault`. |
13 |
| -* The `nonisolated(nonsending)` modifier specifies that a function must always |
14 |
| - run on the caller's actor. |
15 |
| - This is the default behavior with `NonisolatedNonsendingByDefault`. |
| 6 | +## Overview |
| 7 | + |
| 8 | +Prior to this feature, nonisolated async functions *never* run on an actor's executor and instead |
| 9 | +switch to global generic executor. The motivation was to prevent unnecessary serialization and |
| 10 | +contention for the actor by switching off of the actor to run the nonisolated async function, but |
| 11 | +this does have a number of unfortunate consequences: |
| 12 | +1. `nonisolated` is difficult to understand |
| 13 | +2. Async functions that run on the caller's actor are difficult to express |
| 14 | +3. It's easy to write invalid async APIs |
| 15 | +4. It's difficult to write higher-order async APIs |
| 16 | + |
| 17 | +Introduced in Swift 6.2, `-enable-upcoming-feature NonisolatedNonsendingByDefault` changes the |
| 18 | +execution semantics of nonisolated async functions to always run on the caller's actor by default. A |
| 19 | +new `@concurrent` attribute can be added in order to specify that a function must *always* switch |
| 20 | +off of an actor to run. |
| 21 | +```swift |
| 22 | +struct S: Sendable { |
| 23 | + func performSync() {} |
| 24 | + |
| 25 | + // `nonisolated(nonsending)` is the default |
| 26 | + func performAsync() async {} |
| 27 | + |
| 28 | + @concurrent |
| 29 | + func alwaysSwitch() async {} |
| 30 | +} |
| 31 | + |
| 32 | +actor MyActor { |
| 33 | + let s: Sendable |
| 34 | + |
| 35 | + func call() async { |
| 36 | + s.performSync() // runs on actor's executor |
| 37 | + |
| 38 | + await s.performAsync() // runs on actor's executor |
| 39 | + |
| 40 | + s.alwaysSwitch() // switches to global generic executor |
| 41 | + } |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +A `nonisolated(nonsending)` modifier can also be used prior to enabling this upcoming feature in |
| 46 | +order to run on the caller's actor. To achieve the same semantics as above, `S` in this case would |
| 47 | +instead be: |
| 48 | +```swift |
| 49 | +struct S: Sendable { |
| 50 | + func performSync() {} |
| 51 | + |
| 52 | + nonisolated(nonsending) |
| 53 | + func performAsync() async {} |
| 54 | + |
| 55 | + // `@concurrent` is the default |
| 56 | + func alwaysSwitch() async {} |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | + |
| 61 | +## Migration |
| 62 | + |
| 63 | +```sh |
| 64 | +-enable-upcoming-feature NonisolatedNonsendingByDefault:migrate |
| 65 | +``` |
| 66 | + |
| 67 | +Enabling migration for `NonisolatedNonsendingByDefault` adds fix-its that aim to keep the current |
| 68 | +async semantics by adding `@concurrent` to all existing nonisolated async functions and closures. |
| 69 | + |
| 70 | + |
| 71 | +## See Also |
| 72 | + |
| 73 | +- [SE-0461: Run nonisolated async functions on the caller's actor by default](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md) |
| 74 | + |
0 commit comments