SE-0316: Global Actors

I feel like synchronous guarantees between @MainActor code are well-covered, since they're identical to synchronous access in the general actor model. What "other code that happens to run on the main executor" are you thinking about? The operation passed to DispatchQueue.main.async? Objective-C code?

We're not doing anything to prevent other instances of the actor, and I'm not sure we need to: only the instance returned by shared is special. One could presumably create some kind of shared implementation that produces a unique actor instance at each call, and we'd lose the guarantee that everything is getting serialized properly, but that doesn't seem like something we should defend against.

You can't add a global actor annotation because you'll break any caller that isn't on that global actor. Removing a global actor annotation is okay in some cases---but not from anywhere that one could infer from. The proposal states a more restrictive requirement because the latter is a bit hard to describe well:

A global actor attribute (such as @MainActor) can neither be added nor removed from an API; either will cause breaking changes for source code that uses the API.

Sure, I'll update the protocol text.

The former. Without this inference, the write to globalTextSize would be ill-formed.

The intention of the proposal is to require this, but it's a point that absolutely needs discussion. I think the issue of unannotated global/static variables is large enough that we should subset it out.

Unclear! The current model of "global" variables in playgrounds and scripts isn't even memory safe without concurrency in the mix, so we need a larger rethink here. If we treat global variables within scripts/playgrounds as if they were locals, there's nothing to do because they fall under the local capture rules.

Unrelated to any of the above, @John_McCall and I were talking about turning the ad hoc protocol for global actors into a formal protocol, e.g.,

protocol GlobalActor {
  associatedtype ActorType: Actor
  static var shared: ActorType { get }
}

It helps formalize the relationship between global actors and actors. We'd then extend the protocol with custom executors to eliminate the "hop" through the actor, e.g.

protocol GlobalActor {
  associatedtype ActorType: Actor
  static var shared: ActorType { get }
  static var sharedUnownedExecutor: UnownedExecutorRef { get }
  // note: we require that shared.unownedExecutor == sharedUnownedExecutor
}

Doug

2 Likes