Skip to content

Commit 3a9fc92

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

File tree

1 file changed

+94
-60
lines changed

1 file changed

+94
-60
lines changed

proposals/0461-async-function-isolation.md

Lines changed: 94 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function always switches off of an actor to run.
2626
- [Motivation](#motivation)
2727
- [Proposed solution](#proposed-solution)
2828
- [Detailed design](#detailed-design)
29-
- [The `@execution` attribute](#the-execution-attribute)
29+
- [The `@execution` and `@concurrent` attributes](#the-execution-and-concurrent-attributes)
3030
- [`@execution(caller)` functions](#executioncaller-functions)
3131
- [`@execution(concurrent)` functions](#executionconcurrent-functions)
3232
- [Task isolation inheritance](#task-isolation-inheritance)
@@ -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,34 +232,28 @@ 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

249-
### The `@execution` attribute
249+
### The `@execution` and `@concurrent` attributes
250250

251-
`@execution` is a declaration and type attribute that specifies the execution
252-
semantics of an async function. `@execution` must be written with an argument
253-
of either `caller` or `concurrent`. The details of each argument are specified
254-
in the following sections.
251+
`@execution(caller)` and `@concurrent` are both declaration and type attributes
252+
that specify the execution semantics of an async function. The details of
253+
each attribute are specified in the following sections.
255254

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-
261-
Only (implicitly or explicitly) `nonisolated` functions can be marked with the
262-
`@execution` attribute; it is an error to use the `@execution` attribute with
255+
Only (implicitly or explicitly) `nonisolated` functions can be marked with
256+
`@execution(caller)` or `@concurrent`; it is an error to use the these attributes with
263257
an isolation other than `nonisolated`, including global actors, isolated
264258
parameters, and `@isolated(any)`:
265259

@@ -280,13 +274,15 @@ actor MyActor {
280274
}
281275
```
282276

283-
The `@execution` attribute can be used together with `@Sendable` or `sending`.
277+
`@execution(caller)` and `@concurrent` can be used together with `@Sendable` or
278+
`sending`.
284279

285-
The `@execution` attribute is preserved in the type system so that the execution
286-
semantics can be distinguished for function vales.
280+
`@execution(caller)` and `@concurrent` are preserved in the type system so that
281+
the execution semantics can be distinguished for function vales.
287282

288-
The `@execution` attribute cannot be applied to synchronous functions. This is
289-
an artificial limitation that could later be lifted if use cases arise.
283+
`@execution(caller)` and `@concurrent` cannot be applied to synchronous
284+
functions. This is an artificial limitation that could later be lifted if use
285+
cases arise.
290286

291287
#### `@execution(caller)` functions
292288

@@ -349,23 +345,23 @@ In the above code, the calls to `closure` from `callSendableClosure` run on the
349345
main actor, because `closure` is `@execution(caller)` and `callSendableClosure`
350346
is main actor isolated.
351347

352-
#### `@execution(concurrent)` functions
348+
#### `@concurrent` functions
353349

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

357353
```swift
358354
struct S: Sendable {
359-
@execution(concurrent)
355+
@concurrent
360356
func alwaysSwitch() async { ... }
361357
}
362358
```
363359

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

368-
When an `@execution(concurrent)` function is called from a context that can
364+
When an `@concurrent` function is called from a context that can
369365
run on an actor, including `@execution(caller)` functions or actor-isolated
370366
functions, sendable checking is performed on the argument and result values.
371367
Either the argument and result values must have a type that conforms to
@@ -375,7 +371,7 @@ outside of the actor:
375371
```swift
376372
class NotSendable {}
377373

378-
@execution(concurrent)
374+
@concurrent
379375
func alwaysSwitch(ns: NotSendable) async { ... }
380376

381377
actor MyActor {
@@ -395,7 +391,7 @@ actor MyActor {
395391
Unstructured tasks created in nonisolated functions never run on an actor
396392
unless explicitly specified. This behavior is consistent for all nonisolated
397393
functions, including synchronous functions, `@execution(caller)` async
398-
functions, and `@execution(concurrent)` async functions.
394+
functions, and `@concurrent` async functions.
399395

400396
For example:
401397

@@ -483,7 +479,7 @@ struct Program {
483479
}
484480
```
485481

486-
In an `@execution(concurrent)` function, the `#isolation` macro expands to
482+
In an `@concurrent` function, the `#isolation` macro expands to
487483
`nil`.
488484

489485
### Isolation inference for closures
@@ -617,21 +613,21 @@ conversion rules for synchronous `nonisolated` functions and asynchronous
617613
`@execution(caller) nonisolated` functions are the same; they are both
618614
represented under the "Nonisolated" category in the table:
619615

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 |
616+
| Old isolation | New isolation | Crosses Boundary |
617+
|----------------------|------------------------|------------------|
618+
| Nonisolated | Actor isolated | No |
619+
| Nonisolated | `@isolated(any)` | No |
620+
| Nonisolated | `@concurrent` | Yes |
621+
| Actor isolated | Actor isolated | Yes |
622+
| Actor isolated | `@isolated(any)` | No |
623+
| Actor isolated | Nonisolated | Yes |
624+
| Actor isolated | `@concurrent` | Yes |
625+
| `@isolated(any)` | Actor isolated | Yes |
626+
| `@isolated(any)` | Nonisolated | Yes |
627+
| `@isolated(any)` | `@concurrent` | Yes |
628+
| `@concurrent` | Actor isolated | Yes |
629+
| `@concurrent` | `@isolated(any)` | No |
630+
| `@concurrent` | Nonisolated | Yes |
635631

636632
#### Non-`@Sendable` function conversions
637633

@@ -671,7 +667,7 @@ if the original function must leave the actor in order to be called:
671667
```swift
672668
@execution(caller)
673669
func convert(
674-
fn1: @escaping @execution(concurrent) () async -> Void,
670+
fn1: @escaping @concurrent () async -> Void,
675671
) async {
676672
let fn2: @MainActor () async -> Void = fn1 // error
677673

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

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

723719
```swift
@@ -729,7 +725,7 @@ all other async functions switch to the isolated actor's executor.
729725
// switch to main actor executor
730726
}
731727

732-
@execution(concurrent) func runOnGenericExecutor() async {
728+
@concurrent func runOnGenericExecutor() async {
733729
// switch to generic executor
734730

735731
await Task { @MainActor in
@@ -803,14 +799,14 @@ not change -- it will not be gated behind the upcoming feature.
803799

804800
This proposal changes the semantics of nonisolated async functions when the
805801
upcoming feature flag is enabled. Without the upcoming feature flag, the default
806-
for nonisolated async functions is `@execution(concurrent)`. When the upcoming
802+
for nonisolated async functions is `@concurrent`. When the upcoming
807803
feature flag is enabled, the default for nonisolated async functions changes to
808804
`@execution(caller)`. This applies to both function declarations and function
809805
values that are nonisolated (either implicitly or explicitly).
810806

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

840836
```swift
841-
#if hasAttribute(execution)
842-
@execution(concurrent)
837+
#if hasAttribute(concurrent)
838+
@concurrent
843839
#endif
844840
public func myAsyncAPI() async { ... }
845841
```
@@ -863,7 +859,7 @@ public func myAsyncFunc() async {
863859
// original implementation
864860
}
865861

866-
@execution(concurrent)
862+
@concurrent
867863
@_silgen_name(...) // to preserve the original symbol name
868864
@usableFromInline
869865
internal func abi_myAsyncFunc() async {
@@ -880,7 +876,7 @@ can be made inlinable.
880876

881877
`@execution(caller)` functions must accept an implicit actor parameter. This
882878
means that adding `@execution(caller)` to a function that is actor-isolated, or
883-
changing a function from `@execution(concurrent)` to `@execution(caller)`, is
879+
changing a function from `@concurrent` to `@execution(caller)`, is
884880
not a resilient change.
885881

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

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

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

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

955985
## Revisions
956986

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

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

0 commit comments

Comments
 (0)