Skip to content

Commit f1c56a9

Browse files
committed
[SE-0461] Rename @execution(concurrent) to @concurrent.
1 parent be55df5 commit f1c56a9

File tree

1 file changed

+80
-47
lines changed

1 file changed

+80
-47
lines changed

proposals/0461-async-function-isolation.md

Lines changed: 80 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ behavior of existing code, so the change is gated behind the
201201
`AsyncCallerExecution` upcoming feature flag. To help stage in the new
202202
behavior, a new `@execution` attribute can be used to explicitly specify the
203203
execution semantics of an async function in any language mode. The
204-
`@execution(concurrent)` attribute is an explicit spelling for the behavior of
204+
`@concurrent` attribute is an explicit spelling for the behavior of
205205
async functions in language modes <= Swift 6, and the `@execution(caller)`
206206
attribute is an explicit spelling for async functions that run on the caller's
207207
actor.
@@ -215,7 +215,7 @@ class NotSendable {
215215
@execution(caller)
216216
func performAsync() async { ... }
217217

218-
@execution(concurrent)
218+
@concurrent
219219
func alwaysSwitch() async { ... }
220220
}
221221

@@ -232,18 +232,18 @@ actor MyActor {
232232
}
233233
```
234234

235-
`@execution(concurrent)` is the current default for nonisolated async
235+
`@concurrent` is the current default for nonisolated async
236236
functions. `@execution(caller)` will become the default for async functions
237237
when the `AsyncCallerExecution` upcoming feature is enabled.
238238

239239
## Detailed design
240240

241-
The sections below will explicitly use `@execution(concurrent)` and
241+
The sections below will explicitly use `@concurrent` and
242242
`@execution(caller)` to demonstrate examples that will behave consistently
243243
independent of upcoming features or language modes. However, note that the
244244
end state under the `AsyncCallerExecution` upcoming feature will mean that
245245
`@execution(caller)` is not necessary to explicitly write, and
246-
`@execution(concurrent)` will likely be used sparingly because it has far
246+
`@concurrent` will likely be used sparingly because it has far
247247
stricter data-race safety requirements.
248248

249249
### The `@execution` attribute
@@ -253,11 +253,6 @@ semantics of an async function. `@execution` must be written with an argument
253253
of either `caller` or `concurrent`. The details of each argument are specified
254254
in the following sections.
255255

256-
> _Naming rationale_: The term `concurrent` in `@execution(concurrent)` was
257-
> chosen because the colloquial phrase "runs concurrently with actors" is a
258-
> good way to describe the semantics of the function execution. Similarly, the
259-
> async function can be described as running on the concurrent executor.
260-
261256
Only (implicitly or explicitly) `nonisolated` functions can be marked with the
262257
`@execution` attribute; it is an error to use the `@execution` attribute with
263258
an isolation other than `nonisolated`, including global actors, isolated
@@ -349,23 +344,23 @@ In the above code, the calls to `closure` from `callSendableClosure` run on the
349344
main actor, because `closure` is `@execution(caller)` and `callSendableClosure`
350345
is main actor isolated.
351346

352-
#### `@execution(concurrent)` functions
347+
#### `@concurrent` functions
353348

354349
Async functions can be declared to always switch off of an actor to run using
355-
the `@execution(concurrent)` attribute:
350+
the `@concurrent` attribute:
356351

357352
```swift
358353
struct S: Sendable {
359-
@execution(concurrent)
354+
@concurrent
360355
func alwaysSwitch() async { ... }
361356
}
362357
```
363358

364-
The type of an `@execution(concurrent)` function declaration is an
365-
`@execution(concurrent)` function type. Details on function conversions are
359+
The type of an `@concurrent` function declaration is an
360+
`@concurrent` function type. Details on function conversions are
366361
covered in a [later section](#function-conversions).
367362

368-
When an `@execution(concurrent)` function is called from a context that can
363+
When an `@concurrent` function is called from a context that can
369364
run on an actor, including `@execution(caller)` functions or actor-isolated
370365
functions, sendable checking is performed on the argument and result values.
371366
Either the argument and result values must have a type that conforms to
@@ -375,7 +370,7 @@ outside of the actor:
375370
```swift
376371
class NotSendable {}
377372

378-
@execution(concurrent)
373+
@concurrent
379374
func alwaysSwitch(ns: NotSendable) async { ... }
380375

381376
actor MyActor {
@@ -395,7 +390,7 @@ actor MyActor {
395390
Unstructured tasks created in nonisolated functions never run on an actor
396391
unless explicitly specified. This behavior is consistent for all nonisolated
397392
functions, including synchronous functions, `@execution(caller)` async
398-
functions, and `@execution(concurrent)` async functions.
393+
functions, and `@concurrent` async functions.
399394

400395
For example:
401396

@@ -483,7 +478,7 @@ struct Program {
483478
}
484479
```
485480

486-
In an `@execution(concurrent)` function, the `#isolation` macro expands to
481+
In an `@concurrent` function, the `#isolation` macro expands to
487482
`nil`.
488483

489484
### Isolation inference for closures
@@ -617,21 +612,21 @@ conversion rules for synchronous `nonisolated` functions and asynchronous
617612
`@execution(caller) nonisolated` functions are the same; they are both
618613
represented under the "Nonisolated" category in the table:
619614

620-
| Old isolation | New isolation | Crosses Boundary |
621-
|--------------------------|----------------------------|------------------|
622-
| Nonisolated | Actor isolated | No |
623-
| Nonisolated | `@isolated(any)` | No |
624-
| Nonisolated | `@execution(concurrent)` | Yes |
625-
| Actor isolated | Actor isolated | Yes |
626-
| Actor isolated | `@isolated(any)` | No |
627-
| Actor isolated | Nonisolated | Yes |
628-
| Actor isolated | `@execution(concurrent)` | Yes |
629-
| `@isolated(any)` | Actor isolated | Yes |
630-
| `@isolated(any)` | Nonisolated | Yes |
631-
| `@isolated(any)` | `@execution(concurrent)` | Yes |
632-
| `@execution(concurrent)` | Actor isolated | Yes |
633-
| `@execution(concurrent)` | `@isolated(any)` | No |
634-
| `@execution(concurrent)` | Nonisolated | Yes |
615+
| Old isolation | New isolation | Crosses Boundary |
616+
|----------------------|------------------------|------------------|
617+
| Nonisolated | Actor isolated | No |
618+
| Nonisolated | `@isolated(any)` | No |
619+
| Nonisolated | `@concurrent` | Yes |
620+
| Actor isolated | Actor isolated | Yes |
621+
| Actor isolated | `@isolated(any)` | No |
622+
| Actor isolated | Nonisolated | Yes |
623+
| Actor isolated | `@concurrent` | Yes |
624+
| `@isolated(any)` | Actor isolated | Yes |
625+
| `@isolated(any)` | Nonisolated | Yes |
626+
| `@isolated(any)` | `@concurrent` | Yes |
627+
| `@concurrent` | Actor isolated | Yes |
628+
| `@concurrent` | `@isolated(any)` | No |
629+
| `@concurrent` | Nonisolated | Yes |
635630

636631
#### Non-`@Sendable` function conversions
637632

@@ -671,7 +666,7 @@ if the original function must leave the actor in order to be called:
671666
```swift
672667
@execution(caller)
673668
func convert(
674-
fn1: @escaping @execution(concurrent) () async -> Void,
669+
fn1: @escaping @concurrent () async -> Void,
675670
) async {
676671
let fn2: @MainActor () async -> Void = fn1 // error
677672

@@ -717,7 +712,7 @@ functions do not have the ability to switch executors. If a call to a
717712
synchronous function crosses an isolation boundary, the call must happen in an
718713
async context and the executor switch happens at the caller.
719714

720-
`@execution(concurrent)` async functions switch to the generic executor, and
715+
`@concurrent` async functions switch to the generic executor, and
721716
all other async functions switch to the isolated actor's executor.
722717

723718
```swift
@@ -729,7 +724,7 @@ all other async functions switch to the isolated actor's executor.
729724
// switch to main actor executor
730725
}
731726

732-
@execution(concurrent) func runOnGenericExecutor() async {
727+
@concurrent func runOnGenericExecutor() async {
733728
// switch to generic executor
734729

735730
await Task { @MainActor in
@@ -803,14 +798,14 @@ not change -- it will not be gated behind the upcoming feature.
803798

804799
This proposal changes the semantics of nonisolated async functions when the
805800
upcoming feature flag is enabled. Without the upcoming feature flag, the default
806-
for nonisolated async functions is `@execution(concurrent)`. When the upcoming
801+
for nonisolated async functions is `@concurrent`. When the upcoming
807802
feature flag is enabled, the default for nonisolated async functions changes to
808803
`@execution(caller)`. This applies to both function declarations and function
809804
values that are nonisolated (either implicitly or explicitly).
810805

811806
Changing the default execution semantics of nonisolated async functions has
812807
minor source compatibility impact if the implementation calls an
813-
`@execution(concurrent)` function and passes non-Sendable state in the actor's
808+
`@concurrent` function and passes non-Sendable state in the actor's
814809
region. In addition to the source compatibility impact, the change can also
815810
regress performance of existing code if, for example, a specific async function
816811
relied on running off of the main actor when called from the main actor to
@@ -838,8 +833,8 @@ maintaining compatibility with tools versions back to Swift 5.8 when
838833
`hasAttribute` was introduced:
839834

840835
```swift
841-
#if hasAttribute(execution)
842-
@execution(concurrent)
836+
#if hasAttribute(concurrent)
837+
@concurrent
843838
#endif
844839
public func myAsyncAPI() async { ... }
845840
```
@@ -863,7 +858,7 @@ public func myAsyncFunc() async {
863858
// original implementation
864859
}
865860

866-
@execution(concurrent)
861+
@concurrent
867862
@_silgen_name(...) // to preserve the original symbol name
868863
@usableFromInline
869864
internal func abi_myAsyncFunc() async {
@@ -880,7 +875,7 @@ can be made inlinable.
880875

881876
`@execution(caller)` functions must accept an implicit actor parameter. This
882877
means that adding `@execution(caller)` to a function that is actor-isolated, or
883-
changing a function from `@execution(concurrent)` to `@execution(caller)`, is
878+
changing a function from `@concurrent` to `@execution(caller)`, is
884879
not a resilient change.
885880

886881
## Alternatives considered
@@ -895,7 +890,7 @@ potential compromise is to keep the current isolation inference behavior, and
895890
offer fix-its to capture the actor if there are any data-race safety errors
896891
from capturing state in the actor's region.
897892

898-
### Use `nonisolated` instead of a separate `@execution(concurrent)` attribute
893+
### Use `nonisolated` instead of a separate `@concurrent` attribute
899894

900895
It's tempting to not introduce a new attribute to control where an async
901896
function executes, and instead control this behavior with an explicit
@@ -906,9 +901,43 @@ reasons:
906901
`nonisolated` by default, regardless of whether it's applied to synchronous
907902
or async functions.
908903
2. This approach cuts off the future direction of allowing
909-
`@execution(concurrent)` on synchronous functions.
910-
911-
### Use "isolation" terminology instead of "execution"
904+
`@concurrent` on synchronous functions.
905+
906+
### Alternative syntax choices
907+
908+
This proposal was originally pitched using the `@concurrent` syntax, and many
909+
reviewers surfaced objects about why `@concurrent` may be misleading, such as:
910+
911+
* `@concurrent` is not the only source of concurrency; concurrency can arise from
912+
many other things.
913+
* The execution of an `@concurrent` function is not concurrent from the local
914+
perspective of the current task.
915+
916+
It's true that concurrency can only arise if there are multiple "impetuses"
917+
(such as tasks or event sources) in the program that are running with different
918+
isolation. But for the most part, we can assume that there are multiple
919+
impetuses; and while those impetuses might otherwise share isolation,
920+
`@concurrent` is the only isolation specification under this proposal that
921+
guarantees that they do not and therefore forces concurrency. Indeed, we expect
922+
that programmers will be reaching for `@concurrent` exactly for that reason:
923+
they want the current function to run concurrently with whatever else might
924+
happen in the process. So, this proposal uses `@concurrent` because out of the
925+
other alternatives we explored, it best reflects the programmer's intent for
926+
using the attribute.
927+
928+
A previous iteration of this proposal used the syntax `@execution(concurrent)`
929+
instead of `@concurrent`. The review thread explored several variations of
930+
this syntax, including `@executor(concurrent)` and `@executor(global)`.
931+
932+
However, `@execution` or `@executor` encourages
933+
thinking about async function semantics in terms of the lower level model of
934+
executors and threads, and we should be encouraging programmers to think about
935+
these semantics at the higher abstraction level of actor isolation and tasks.
936+
Trying to understand the semantics in proposal in terms of executors can also
937+
be misleading, both because isolation does not always map naively to executor
938+
requests and because executors are used for other things than isolation.
939+
For example, an `@executor(global)` function could end up running on some
940+
executor other than the global executor via task executor preferences.
912941

913942
Another possibility is to use isolation terminology instead of `@execution`
914943
for the syntax. This direction does not accomplish the goal of having a
@@ -954,6 +983,10 @@ function reference instead of when the function is called.
954983

955984
## Revisions
956985

986+
The proposal was revised with the following changes after the first review:
987+
988+
* Renamed `@execution(concurrent)` back to `@concurrent`.
989+
957990
The proposal was revised with the following changes after the pitch discussion:
958991

959992
* Gate the behavior change behind an `AsyncCallerExecution` upcoming feature

0 commit comments

Comments
 (0)