I'm glad you brought this up, because it reminded me that there can be a subtle difference between the parameter region and the caller's actor region. And, I think there's a way in which @execution(caller) nonisolated
functions can be meaningfully different from methods with an isolated parameter. Yes, you're right that any local variables derived values in the parameter region are also in the parameter region. For methods with an isolated parameter, the parameter region is always the actor's region. However, for nonisolated
methods that run on the caller's actor, I don't think there's a way to actually get at the actor and assign into its state unless the parameters are already part of the actor's region before the call happens. If I'm right about that, then we don't have to always merge parameters and results into the actor's region. This allows us to have the same region rules for nonisolated
synchronous functions and @execution(caller) nonisolated
functions.
Let's make this more concrete. Consider the following code:
class NotSendable {}
nonisolated func identity<T>(_ t: T) -> T {
return t
}
actor MyActor {
func isolatedToSelf() -> sending NotSendable {
let ns = NotSendable()
return identity(ns)
}
}
The above code is valid under -langauge-mode 6
. The local ns
variable is in a disconnected region, and the result of identity(ns)
is in the same disconnected region as ns
. So, returning that value as a sending
result is fine. Even though identity
ran on the actor, the values were not merged into the actor's region.
If you give identity
a value already in the actor's region, then the code is invalid:
class NotSendable {}
nonisolated func identity<T>(_ t: T) -> T {
return t
}
actor MyActor {
let ns = NotSendable()
func isolatedToSelf() -> sending NotSendable {
return identity(ns) // error: actor isolated value cannot be sent
}
}
I think these region rules are still valid if identity
is async
:
class NotSendable {}
@execution(caller)
nonisolated func identity<T>(_ t: T) async -> T {
return t
}
actor MyActor {
func isolatedToSelf() async -> sending NotSendable {
let ns = NotSendable()
return await identity(ns)
}
}
I think the above code should be valid. The implementation of identity
can't actually access the actor's state unless isolated state is passed in via one of the parameters. Yes, the proposal allows you to access #isolation
for the purpose of forwarding it along to some method that accepts an isolated (any Actor)?
parameter, but rule is meant to help with the transition off of isolated parameters, and I think it's still safe; I don't think there's a way to get at the actor's state, because the Actor
protocol does not give you any way to get isolated state out (you can if the isolated parameter has a concrete type).