@@ -513,15 +513,14 @@ actor Actor {
513513 var field: NonSendable
514514
515515 func method () {
516- // ns is in a region that is disconnected .
516+ // ns is in a disconnected isolation region .
517517 let ns = NonSendable ()
518518 ...
519519 }
520520}
521521
522522func nonisolatedFunction () async {
523- // ns is again in a region that is disconnected
524- // domain.
523+ // ns is in a disconnected isolation region.
525524 let ns = NonSendable ()
526525}
527526
@@ -692,55 +691,6 @@ loosened via the usage of the `disconnected` field attribute. A `disconnected`
692691field of an actor is a field that is part of a separate isolation region from
693692the actor. We discuss this an extension below.
694693
695- ### Function Parameters
696-
697- A function's non-` Sendable ` parameters are considered to be part of the same
698- region due to the region merging rules. If a function is a method or a global
699- actor isolated function, then the function parameters are considered to be within
700- an actor isolated region. If a function is a nonisolated async function then the
701- function parameters are part of a disconnected region with the additional
702- restriction that the function argument region cannot be transferred. The reason
703- why they cannot be transferred is that in such a function, we do not know if an
704- isolation boundary was crossed when our caller called the function:
705-
706- ``` swift
707- // If we were called by isolatedCaller, we would know that x was transferred
708- // and could transfer it across another isolation boundary. But if we are called
709- // by nonIsolatedCaller, we can't, so since we don't know our caller we must
710- // be conservatively correct.
711- func nonIsolatedCallee (_ x : NonSendable) async { ... }
712-
713- func nonIsolatedCaller () async {
714- let x = NonSendable ()
715-
716- // No transfer occurs since callee is also nonisolated.
717- await nonIsolatedCallee (x)
718-
719- // So x could be used here.
720- useValue (x)
721- }
722-
723- @MainActor func isolatedCaller () async {
724- let x = NonSendable ()
725-
726- // A transfer occurs here...
727- await nonIsolatedCallee (x)
728-
729- // Error! So x cannot be used here.
730- useValue (x)
731- }
732- ```
733-
734- This restriction follows from this proposal only defining a * transfer*
735- convention to a callsite if we know that we are crossing an isolation
736- boundary. Thus we must be conservative and treat parameters as being
737- non-transferable so we can handle cases where a callee's invocation does not
738- result in an isolation boundary being crossed. This restriction can be loosened
739- via the introduction of an explicit function argument convention that binds also
740- callers called ` transferring ` that that would cause non-` Sendable ` values to be
741- transferred even when a callee is not an isolation boundary. We discuss this
742- convention as an extension below.
743-
744694#### Merging Isolation Regions
745695
746696Our isolation region rules require us to merge regions when passing two
@@ -822,6 +772,105 @@ our specific kinds of isolation regions:
822772 }
823773 ```
824774
775+
776+ ### Function Parameters
777+
778+ A function's non-` Sendable ` parameters are considered to be part of the same
779+ region due to the region merging rules. If a function is a method or a global
780+ actor isolated function, then the function parameters are considered to be within
781+ an actor isolated region. If a function is a nonisolated async function then the
782+ function parameters are part of a disconnected region with the additional
783+ restriction that the function argument region cannot be transferred. The reason
784+ why they cannot be transferred is that in such a function, we do not know if an
785+ isolation boundary was crossed when our caller called the function:
786+
787+ ``` swift
788+ // If we were called by isolatedCaller, we would know that x was transferred
789+ // and could transfer it across another isolation boundary. But if we are called
790+ // by nonIsolatedCaller, we can't, so since we don't know our caller we must
791+ // be conservatively correct.
792+ func nonIsolatedCallee (_ x : NonSendable) async { ... }
793+
794+ func nonIsolatedCaller () async {
795+ let x = NonSendable ()
796+
797+ // No transfer occurs since callee is also nonisolated.
798+ await nonIsolatedCallee (x)
799+
800+ // So x could be used here.
801+ useValue (x)
802+ }
803+
804+ @MainActor func isolatedCaller () async {
805+ let x = NonSendable ()
806+
807+ // A transfer occurs here...
808+ await nonIsolatedCallee (x)
809+
810+ // Error! So x cannot be used here.
811+ useValue (x)
812+ }
813+ ```
814+
815+ This restriction follows from this proposal only defining a * transfer*
816+ convention to a callsite if we know that we are crossing an isolation
817+ boundary. Thus we must be conservative and treat parameters as being
818+ non-transferable so we can handle cases where a callee's invocation does not
819+ result in an isolation boundary being crossed. This restriction can be loosened
820+ via the introduction of an explicit function argument convention that binds also
821+ callers called ` transferring ` that that would cause non-` Sendable ` values to be
822+ transferred even when a callee is not an isolation boundary. We discuss this
823+ convention as an extension below.
824+
825+ ### ` nonisolated ` functions, disconnected isolation regions, and async let
826+
827+ When we pass a non-` Sendable ` value as an argument to a ` nonisolated ` function,
828+ the value is only temporarily transferred to the function. This can be seen
829+ since:
830+
831+ * A parameter to a ` nonisolated ` function cannot be transferred into a different
832+ isolation domain.
833+ * A ` nonisolated ` function does not have any non-temporary isolated state of its
834+ own that the non-` Sendable ` value could escape into.
835+
836+ Despite this temporarility, an actor isolated isolation region can never be
837+ transferred into a ` nonisolated ` function since the state of the actor is
838+ strongly tied to the actor. But this does mean that a disconnected isolation
839+ region can be used and transferred after the region is transferred to a
840+ ` nonisolated ` function.
841+
842+ While said property holds for simple function applications, it is not so simple
843+ for async let parameters initialized from a nonisolated function. When we pass a
844+ non-` Sendable ` parameter to the nonisolated function, the caller does not know
845+ if the non isolated function has completed running until the async let is
846+ awaited upon. Since we cannot allow for the non-` Sendable ` value to be used
847+ concurrently, we must not allow for the value to be used until the async let is
848+ awaited upon:
849+
850+ ``` swift
851+ let x = NonSendable ()
852+ async let y = nonIsolatedFunc (x)
853+ print (x) // Error! x is used while transferred out of current isolation domain
854+ await y
855+ print (x) // Safe since x is no longer being used by nonIsolatedFunc.
856+ ```
857+
858+ In contrast, if the async let function was specifically isolated to an actor,
859+ then we have transferred away the value into that other isolation domain and can
860+ no longer use it locally:
861+
862+ ``` swift
863+ let x = NonSendable ()
864+ async let y = transferToMainActor (x)
865+ print (x) // Error! x was already transferred into the main actor!
866+ await y
867+ print (x) // Error! x was already transferred into the main actor!
868+ ```
869+
870+ A similar property holds for asynchronous nonisolated coroutines. While the
871+ coroutine is live, we cannot access any disconnected non-` Sendable ` parameters
872+ passed to the coroutine.
873+
825874### non-` Sendable ` Closures
826875
827876Currently non-` Sendable ` closures like other non-` Sendable ` values are not
@@ -879,14 +928,24 @@ actor Actor {
879928
880929In contrast, if a closure is nonisolated and only captures non-` Sendable ` values
881930from a disconnected region, then the resulting region from the closures
882- formation is a disconnected isolation region.
931+ formation is a disconnected isolation region:
932+
933+ ``` swift
934+ extension Actor {
935+ let x = NonSendable ()
936+ // Regions: [(x)]
937+ let closure: () -> () = { print (x) }
938+ // Regions: [(x, closure)]
939+ // ...
940+ }
941+ ```
883942
884- #### Nonisolated Closures
943+ #### Transferring Nonisolated Closures
885944
886945A nonisolated non-` Sendable ` synchronous or asynchronous closure can be
887946transferred into another isolation domain if the closure's region is never
888- used again within the closure's defining context :
889-
947+ used again locally :
948+
890949``` swift
891950extension MyActor {
892951 func synchronousNonIsolatedNonSendableClosure () async {
@@ -965,33 +1024,6 @@ extension Actor {
9651024}
9661025```
9671026
968- ### async let and ` nonisolated ` functions
969-
970- Swift supports future like functionality via async let. When a non-` Sendable `
971- type is passed to a ` nonisolated ` function that is bound to an async let, the
972- value is temporarily transferred outside of the current isolation domain causing
973- the value to be unavailable for use until the async let is awaited upon:
974-
975- ``` swift
976- let x = NonSendable ()
977- async let y = nonIsolatedFunc (x)
978- print (x) // Error! x is used while transferred out of current isolation domain
979- await y
980- print (x) // Safe since x is no longer being used by nonIsolatedFunc.
981- ```
982-
983- In contrast, if the async let function was specifically isolated to an actor,
984- then we have transferred away the value into that other isolation domain and can
985- no longer use it locally:
986-
987- ``` swift
988- let x = NonSendable ()
989- async let y = transferToMainActor (x)
990- print (x) // Error! x was already transferred into the main actor!
991- await y
992- print (x) // Error! x was already transferred into the main actor!
993- ```
994-
9951027### Simplifying ` nonisolated ` initializers and deinitializers via transferring
9961028
9971029All of the above discussions involved actors in the context of an isolated self
0 commit comments