@@ -26,7 +26,7 @@ async function always switches off of an actor to run.
26
26
- [ Motivation] ( #motivation )
27
27
- [ Proposed solution] ( #proposed-solution )
28
28
- [ Detailed design] ( #detailed-design )
29
- - [ The ` @execution ` attribute ] ( #the-execution-attribute )
29
+ - [ The ` @execution ` and ` @concurrent ` attributes ] ( #the-execution-and-concurrent-attributes )
30
30
- [ ` @execution(caller) ` functions] ( #executioncaller-functions )
31
31
- [ ` @execution(concurrent) ` functions] ( #executionconcurrent-functions )
32
32
- [ Task isolation inheritance] ( #task-isolation-inheritance )
@@ -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,34 +232,28 @@ 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
- ### The ` @execution ` attribute
249
+ ### The ` @execution ` and ` @concurrent ` attributes
250
250
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.
255
254
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
263
257
an isolation other than ` nonisolated ` , including global actors, isolated
264
258
parameters, and ` @isolated(any) ` :
265
259
@@ -280,13 +274,15 @@ actor MyActor {
280
274
}
281
275
```
282
276
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 ` .
284
279
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.
287
282
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.
290
286
291
287
#### ` @execution(caller) ` functions
292
288
@@ -349,23 +345,23 @@ In the above code, the calls to `closure` from `callSendableClosure` run on the
349
345
main actor, because ` closure ` is ` @execution(caller) ` and ` callSendableClosure `
350
346
is main actor isolated.
351
347
352
- #### ` @execution( concurrent) ` functions
348
+ #### ` @concurrent ` functions
353
349
354
350
Async functions can be declared to always switch off of an actor to run using
355
- the ` @execution( concurrent) ` attribute:
351
+ the ` @concurrent ` attribute:
356
352
357
353
``` swift
358
354
struct S : Sendable {
359
- @execution ( concurrent)
355
+ @concurrent
360
356
func alwaysSwitch () async { ... }
361
357
}
362
358
```
363
359
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
366
362
covered in a [ later section] ( #function-conversions ) .
367
363
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
369
365
run on an actor, including ` @execution(caller) ` functions or actor-isolated
370
366
functions, sendable checking is performed on the argument and result values.
371
367
Either the argument and result values must have a type that conforms to
@@ -375,7 +371,7 @@ outside of the actor:
375
371
``` swift
376
372
class NotSendable {}
377
373
378
- @execution ( concurrent)
374
+ @concurrent
379
375
func alwaysSwitch (ns : NotSendable) async { ... }
380
376
381
377
actor MyActor {
@@ -395,7 +391,7 @@ actor MyActor {
395
391
Unstructured tasks created in nonisolated functions never run on an actor
396
392
unless explicitly specified. This behavior is consistent for all nonisolated
397
393
functions, including synchronous functions, ` @execution(caller) ` async
398
- functions, and ` @execution( concurrent) ` async functions.
394
+ functions, and ` @concurrent ` async functions.
399
395
400
396
For example:
401
397
@@ -483,7 +479,7 @@ struct Program {
483
479
}
484
480
```
485
481
486
- In an ` @execution( concurrent) ` function, the ` #isolation ` macro expands to
482
+ In an ` @concurrent ` function, the ` #isolation ` macro expands to
487
483
` nil ` .
488
484
489
485
### Isolation inference for closures
@@ -617,21 +613,21 @@ conversion rules for synchronous `nonisolated` functions and asynchronous
617
613
` @execution(caller) nonisolated ` functions are the same; they are both
618
614
represented under the "Nonisolated" category in the table:
619
615
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 |
635
631
636
632
#### Non-` @Sendable ` function conversions
637
633
@@ -671,7 +667,7 @@ if the original function must leave the actor in order to be called:
671
667
``` swift
672
668
@execution (caller)
673
669
func convert (
674
- fn1 : @escaping @execution ( concurrent) () async -> Void ,
670
+ fn1 : @escaping @concurrent () async -> Void ,
675
671
) async {
676
672
let fn2: @MainActor () async -> Void = fn1 // error
677
673
@@ -717,7 +713,7 @@ functions do not have the ability to switch executors. If a call to a
717
713
synchronous function crosses an isolation boundary, the call must happen in an
718
714
async context and the executor switch happens at the caller.
719
715
720
- ` @execution( concurrent) ` async functions switch to the generic executor, and
716
+ ` @concurrent ` async functions switch to the generic executor, and
721
717
all other async functions switch to the isolated actor's executor.
722
718
723
719
``` swift
@@ -729,7 +725,7 @@ all other async functions switch to the isolated actor's executor.
729
725
// switch to main actor executor
730
726
}
731
727
732
- @execution ( concurrent) func runOnGenericExecutor () async {
728
+ @concurrent func runOnGenericExecutor () async {
733
729
// switch to generic executor
734
730
735
731
await Task { @MainActor in
@@ -803,14 +799,14 @@ not change -- it will not be gated behind the upcoming feature.
803
799
804
800
This proposal changes the semantics of nonisolated async functions when the
805
801
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
807
803
feature flag is enabled, the default for nonisolated async functions changes to
808
804
` @execution(caller) ` . This applies to both function declarations and function
809
805
values that are nonisolated (either implicitly or explicitly).
810
806
811
807
Changing the default execution semantics of nonisolated async functions has
812
808
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
814
810
region. In addition to the source compatibility impact, the change can also
815
811
regress performance of existing code if, for example, a specific async function
816
812
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
838
834
` hasAttribute ` was introduced:
839
835
840
836
``` swift
841
- #if hasAttribute(execution )
842
- @execution ( concurrent)
837
+ #if hasAttribute(concurrent )
838
+ @concurrent
843
839
#endif
844
840
public func myAsyncAPI () async { ... }
845
841
```
@@ -863,7 +859,7 @@ public func myAsyncFunc() async {
863
859
// original implementation
864
860
}
865
861
866
- @execution ( concurrent)
862
+ @concurrent
867
863
@_silgen_name (... ) // to preserve the original symbol name
868
864
@usableFromInline
869
865
internal func abi_myAsyncFunc () async {
@@ -880,7 +876,7 @@ can be made inlinable.
880
876
881
877
` @execution(caller) ` functions must accept an implicit actor parameter. This
882
878
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
884
880
not a resilient change.
885
881
886
882
## Alternatives considered
@@ -895,7 +891,7 @@ potential compromise is to keep the current isolation inference behavior, and
895
891
offer fix-its to capture the actor if there are any data-race safety errors
896
892
from capturing state in the actor's region.
897
893
898
- ### Use ` nonisolated ` instead of a separate ` @execution( concurrent) ` attribute
894
+ ### Use ` nonisolated ` instead of a separate ` @concurrent ` attribute
899
895
900
896
It's tempting to not introduce a new attribute to control where an async
901
897
function executes, and instead control this behavior with an explicit
@@ -906,9 +902,43 @@ reasons:
906
902
` nonisolated ` by default, regardless of whether it's applied to synchronous
907
903
or async functions.
908
904
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.
912
942
913
943
Another possibility is to use isolation terminology instead of ` @execution `
914
944
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.
954
984
955
985
## Revisions
956
986
987
+ The proposal was revised with the following changes after the first review:
988
+
989
+ * Renamed ` @execution(concurrent) ` back to ` @concurrent ` .
990
+
957
991
The proposal was revised with the following changes after the pitch discussion:
958
992
959
993
* Gate the behavior change behind an ` AsyncCallerExecution ` upcoming feature
0 commit comments