@@ -201,7 +201,7 @@ behavior of existing code, so the change is gated behind the
201
201
` AsyncCallerExecution ` upcoming feature flag. To help stage in the new
202
202
behavior, a new ` @execution ` attribute can be used to explicitly specify the
203
203
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
205
205
async functions in language modes <= Swift 6, and the ` @execution(caller) `
206
206
attribute is an explicit spelling for async functions that run on the caller's
207
207
actor.
@@ -215,7 +215,7 @@ class NotSendable {
215
215
@execution (caller)
216
216
func performAsync () async { ... }
217
217
218
- @execution ( concurrent)
218
+ @concurrent
219
219
func alwaysSwitch () async { ... }
220
220
}
221
221
@@ -232,18 +232,18 @@ actor MyActor {
232
232
}
233
233
```
234
234
235
- ` @execution( concurrent) ` is the current default for nonisolated async
235
+ ` @concurrent ` is the current default for nonisolated async
236
236
functions. ` @execution(caller) ` will become the default for async functions
237
237
when the ` AsyncCallerExecution ` upcoming feature is enabled.
238
238
239
239
## Detailed design
240
240
241
- The sections below will explicitly use ` @execution( concurrent) ` and
241
+ The sections below will explicitly use ` @concurrent ` and
242
242
` @execution(caller) ` to demonstrate examples that will behave consistently
243
243
independent of upcoming features or language modes. However, note that the
244
244
end state under the ` AsyncCallerExecution ` upcoming feature will mean that
245
245
` @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
247
247
stricter data-race safety requirements.
248
248
249
249
### The ` @execution ` attribute
@@ -253,11 +253,6 @@ semantics of an async function. `@execution` must be written with an argument
253
253
of either ` caller ` or ` concurrent ` . The details of each argument are specified
254
254
in the following sections.
255
255
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
256
Only (implicitly or explicitly) ` nonisolated ` functions can be marked with the
262
257
` @execution ` attribute; it is an error to use the ` @execution ` attribute with
263
258
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
349
344
main actor, because ` closure ` is ` @execution(caller) ` and ` callSendableClosure `
350
345
is main actor isolated.
351
346
352
- #### ` @execution( concurrent) ` functions
347
+ #### ` @concurrent ` functions
353
348
354
349
Async functions can be declared to always switch off of an actor to run using
355
- the ` @execution( concurrent) ` attribute:
350
+ the ` @concurrent ` attribute:
356
351
357
352
``` swift
358
353
struct S : Sendable {
359
- @execution ( concurrent)
354
+ @concurrent
360
355
func alwaysSwitch () async { ... }
361
356
}
362
357
```
363
358
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
366
361
covered in a [ later section] ( #function-conversions ) .
367
362
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
369
364
run on an actor, including ` @execution(caller) ` functions or actor-isolated
370
365
functions, sendable checking is performed on the argument and result values.
371
366
Either the argument and result values must have a type that conforms to
@@ -375,7 +370,7 @@ outside of the actor:
375
370
``` swift
376
371
class NotSendable {}
377
372
378
- @execution ( concurrent)
373
+ @concurrent
379
374
func alwaysSwitch (ns : NotSendable) async { ... }
380
375
381
376
actor MyActor {
@@ -395,7 +390,7 @@ actor MyActor {
395
390
Unstructured tasks created in nonisolated functions never run on an actor
396
391
unless explicitly specified. This behavior is consistent for all nonisolated
397
392
functions, including synchronous functions, ` @execution(caller) ` async
398
- functions, and ` @execution( concurrent) ` async functions.
393
+ functions, and ` @concurrent ` async functions.
399
394
400
395
For example:
401
396
@@ -483,7 +478,7 @@ struct Program {
483
478
}
484
479
```
485
480
486
- In an ` @execution( concurrent) ` function, the ` #isolation ` macro expands to
481
+ In an ` @concurrent ` function, the ` #isolation ` macro expands to
487
482
` nil ` .
488
483
489
484
### Isolation inference for closures
@@ -617,21 +612,21 @@ conversion rules for synchronous `nonisolated` functions and asynchronous
617
612
` @execution(caller) nonisolated ` functions are the same; they are both
618
613
represented under the "Nonisolated" category in the table:
619
614
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 |
635
630
636
631
#### Non-` @Sendable ` function conversions
637
632
@@ -671,7 +666,7 @@ if the original function must leave the actor in order to be called:
671
666
``` swift
672
667
@execution (caller)
673
668
func convert (
674
- fn1 : @escaping @execution ( concurrent) () async -> Void ,
669
+ fn1 : @escaping @concurrent () async -> Void ,
675
670
) async {
676
671
let fn2: @MainActor () async -> Void = fn1 // error
677
672
@@ -717,7 +712,7 @@ functions do not have the ability to switch executors. If a call to a
717
712
synchronous function crosses an isolation boundary, the call must happen in an
718
713
async context and the executor switch happens at the caller.
719
714
720
- ` @execution( concurrent) ` async functions switch to the generic executor, and
715
+ ` @concurrent ` async functions switch to the generic executor, and
721
716
all other async functions switch to the isolated actor's executor.
722
717
723
718
``` swift
@@ -729,7 +724,7 @@ all other async functions switch to the isolated actor's executor.
729
724
// switch to main actor executor
730
725
}
731
726
732
- @execution ( concurrent) func runOnGenericExecutor () async {
727
+ @concurrent func runOnGenericExecutor () async {
733
728
// switch to generic executor
734
729
735
730
await Task { @MainActor in
@@ -803,14 +798,14 @@ not change -- it will not be gated behind the upcoming feature.
803
798
804
799
This proposal changes the semantics of nonisolated async functions when the
805
800
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
807
802
feature flag is enabled, the default for nonisolated async functions changes to
808
803
` @execution(caller) ` . This applies to both function declarations and function
809
804
values that are nonisolated (either implicitly or explicitly).
810
805
811
806
Changing the default execution semantics of nonisolated async functions has
812
807
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
814
809
region. In addition to the source compatibility impact, the change can also
815
810
regress performance of existing code if, for example, a specific async function
816
811
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
838
833
` hasAttribute ` was introduced:
839
834
840
835
``` swift
841
- #if hasAttribute(execution )
842
- @execution ( concurrent)
836
+ #if hasAttribute(concurrent )
837
+ @concurrent
843
838
#endif
844
839
public func myAsyncAPI () async { ... }
845
840
```
@@ -863,7 +858,7 @@ public func myAsyncFunc() async {
863
858
// original implementation
864
859
}
865
860
866
- @execution ( concurrent)
861
+ @concurrent
867
862
@_silgen_name (... ) // to preserve the original symbol name
868
863
@usableFromInline
869
864
internal func abi_myAsyncFunc () async {
@@ -880,7 +875,7 @@ can be made inlinable.
880
875
881
876
` @execution(caller) ` functions must accept an implicit actor parameter. This
882
877
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
884
879
not a resilient change.
885
880
886
881
## Alternatives considered
@@ -895,7 +890,7 @@ potential compromise is to keep the current isolation inference behavior, and
895
890
offer fix-its to capture the actor if there are any data-race safety errors
896
891
from capturing state in the actor's region.
897
892
898
- ### Use ` nonisolated ` instead of a separate ` @execution( concurrent) ` attribute
893
+ ### Use ` nonisolated ` instead of a separate ` @concurrent ` attribute
899
894
900
895
It's tempting to not introduce a new attribute to control where an async
901
896
function executes, and instead control this behavior with an explicit
@@ -906,9 +901,43 @@ reasons:
906
901
` nonisolated ` by default, regardless of whether it's applied to synchronous
907
902
or async functions.
908
903
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.
912
941
913
942
Another possibility is to use isolation terminology instead of ` @execution `
914
943
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.
954
983
955
984
## Revisions
956
985
986
+ The proposal was revised with the following changes after the first review:
987
+
988
+ * Renamed ` @execution(concurrent) ` back to ` @concurrent ` .
989
+
957
990
The proposal was revised with the following changes after the pitch discussion:
958
991
959
992
* Gate the behavior change behind an ` AsyncCallerExecution ` upcoming feature
0 commit comments