I think the only way currently is to pass isolated Actor into a client’s method to make the call isolated to the actor. I wish there was the way to isolate client instance on the caller side, without the need to modify its implementation.
Even if it calls to async methods? I thought this behavior of hopping of to a general executor on non-Sendable objects will remain untouched with region based isolation?
No, because in that case the call to the client will be isolated on the actor and therefore safe. I have migrated part of the code to that approach once Swift 5.10.
As long as the value is still in a separate region, yes, it's fine to pass it to a non-isolated async function, because that function cannot merge it into any other region. If it's already part of the actor's region, then no.
That's correct. The point of region-based isolation is to reason about how values stay disconnected, even across concurrent domains. (In some cases, that will require annotations.)
So in the first case of client being local to the function it would be considered as disconnected? But as soon as it becomes a property on an actor, it is part of an actor region? Seems that was missing for me from the region based isolation.
A non-isolated function that returns a value (such as ClientBootstrap.init) can only possibly connect its return value to any non-Sendable arguments that were passed in. If it takes no arguments (or if all the arguments are Sendable), the caller knows the value must be disconnected. It then stays disconnected until you do something that could possibly connect it.
A non-isolated function, async or not (such as doSomeAsyncWork), can only possibly connect its non-Sendable arguments to each other; after the call, the caller has to assume the arguments might have connected to each other — that is, it must treat them all as part of the same region — but that region will still be disconnected if all the arguments were in disconnected regions before the call.
The only difference that async makes is that a non-isolated async function does run concurrently with any actors, so while you can pass values from the actor's isolated region to a non-isolated sync function (because the actor is effectively still isolated during the call), you can't do that with a non-isolated async function (because the actor is not isolated during the call).
To apply this concretely to the example, client is initialized to a disconnected value, so it's fine to pass that value to a non-isolated async function, and then the value will even still be disconnected after the call. This should all work under basic region-based isolation. If you wanted to pass actor-isolated values to the initializer, then I believe you'd need an attribute under the new proposal to tell the caller that the initializer returns a disconnected value. You'd need the same if you wanted to pass actor-isolated values to doSomeAsyncWork while keeping client disconnected, except that you intrinsically can't ever do that; the only way to make actor state safely available to doSomeAsyncWork would be to make it properly inherit the current actor isolation.