SE-0313: Improved control over actor isolation

Why does this "seem natural"? I cannot point to any other place in the language where a value changes type within a nested context, and we've resisted such changes in the past (e.g., an value x: Int? becoming type Int inside an if x != nil).

We're generally quite careful about adding implicit conversions, because they can have unintended consequences. In this case, the type-directed approach effectively needs two implicit conversions, one in each direction. Going from @sync -> @async is always fine (and needs to happen at some point), but you also need to be able to go from @async -> @sync in certain limited places. This is an "interesting" conversion because it's presence means you are interacting with an actor asynchronously, so each such conversion needs to be part of something that can be done asynchronously (function call, read from an actor property, etc.) and must be covered by an await.

That's a fair point. I suppose I was thinking about how the sequence of conversions you take can affect type identity, which I find a bit odd. For example, consider this:

func f(a: @sync MyActor) {
  let a1: Any = a
  let a2: Sendable = a
  let a3: Any = a2
}

What are the types dynamically stored in a1 and a3? They're actually different, because it's a1 will have dynamic type @sync MyActor, while a3 will have dynamic type @async MyActor because it had to be Sendable to initialize a2. Per your comment about optional and conversions, I bet you can construct a similar case with Optional<T>, so maybe this oddity is a big deal.

Let's go more dynamic, though. What if now I write:

if let a4 = a3 as? @sync MyActor { ... }

Does this dynamic cast fail, because the type stored in a3 is @async MyActor, or is the runtime dynamically checking whether we are currently executing on the this actor and allow the cast to succeed?

Happy to discuss!

There's more rationale in one of the early pitch thread documents, but essentially the argument is that the self of an actor shouldn't be special: you should be able to write a free functions and closures that can have synchronous access to the actors, for example, because that's normal factoring of code. It's very similar to how mutating methods of structs/enums are really just inout on the self parameter, so mutating just isn't all that special.

isolated parameters on closures also let you write things like the runOnActor operation mentioned above.

Doug

2 Likes