SE-0306: Actors

Yeah, I loved the global actors proposal. In fact, I see it as the nail in the coffin of the argument of compatibility with existing codebase that use classes. Also, I actually see @JJJ's argument in complete opposite light. Given that one can add isolation to existing classes with global actors, effectively turning classes into actors, then there's no reason at all, IMO, to bring inheritance to actors. If you really really want inheritance and isolation, use classes and global actors. I now definitely don't see any reason to bring inheritance to actors.

Classes are not a subset of actors. You can change the state of a class object from multiple threads - you can not do that with an actor. That is the benefit you get from actors - and it is the reason why you can not simply change your classes to actors.

Holy cow, you are right! Totally missed that.

This is incorrect. See the other thread.

I agree it goes both ways :+1:

I guess what I'm asking, really, is whether the Swift community wants to add this extra complexity to the language (both specification, implementation, and learning), or perhaps just want to piggyback on the existing "class" type.

1 Like

Apparently, what I said is not true according to @xwu in the other thread.

@xwu this reasoning would be incorrect, then?

Yeah, I was wrong. As soon as you have more than one instance, this doesn't hold. You would not be able to create a hierarchy of actors where two such "actors" would be truly isolated by each instance having its own execution context. I'm not sure that matters, though. But they would not be real actors.

Hmm. Are there any negative consequences of making actors share the same execution context? If a runtime is allowed to run this on a machine without any threading, then everything would basically just run on the MainActor, right? Is that allowed?

So actors would not be able to execute work in parallel, of course. But apart from that?

Is the Swift Concurrency Runtime allowed to merge execution contexts at will? Are there any rules for when that is OK?

Personally, I'd remove actor and simply replace it with isolated class. We can then rethink of everything in term of isolation alone:

// the class has its own isolation context (it's an actor)
isolated class MyActor {
   func isolatedMember() { ... }
   nonisolated func nonisolatedMember() { ... }
}
// the class is attached to a global actor isolation context
isolated(MainActor) class MyObject {
   func isolatedMember() { ... }
   nonisolated func nonisolatedMember() { ... }
}
// the class has no isolation context, but one of its member is isolated to the main actor
class MyObject {
   func nonisolatedMember() { ... }
   isolated(MainActor) func isolatedMember() { ... }
}

I know nonisolated and global actors where split from this proposal in order to make it simpler, but I'm not sure those splits were warranted. It seems to me we might be slicing things into too many concepts for no good reason, and that it ultimately makes things complicated.

6 Likes

This is not a bad idea actually!

For one thing, that solves the inconsistency where "an instance of a class is an object" but "an instance of an actor is an actor".

Could one have an "isolated struct" or an "isolated enum"? What would that mean? As far as I understand we can apply global actors to structs and enums, could one "isolate" any type?

1 Like

I don't think isolated struct or isolated enum makes sense because those types are passed by copy. Each actor having access to the value is going to have its own copy so there's no point in each copy having an execution context of its own. It's a bit like asking for indirect class: pointless.

But it does makes sense with global actors, as you mentioned, because the execution context is not part of the struct or enum.

Structured concurrency provides isolation as well, and both do so by building on SE-0302.

Actors are a declarative frontend to isolation, just as structured concurrency is the procedural interface to them. Calling one or the other the "isolation" system seems misleading and is technically incorrect.

-Chris

9 Likes

Indeed. But I was referring to the upcoming proposals about global actors and nonisolated which are also declarative frontends to isolation. Together actor + @MainActor + nonisolated are three very different ways to spell the same thing: which execution context can run a particular function. (And maybe @Sendable is kind of one too.) Can't we think of a way to make similar things more similar?

I am wondering if an actor / isolate should have methods after all or if it would be more like a container for objects and you would call methods on exposed objects of it instead. This way it is clear, that an actor is not a class but something different.

Would it make sense to look into a possible constraint spelling such as actor protocol Actor: ... {} in the future? Sure the typealias AnyObject = class is still a bit strange, but constraining protocols to type kinds would still be useful for library vendors. I think it would be additive to fix the declaration of the Actor protocol in such a way in the future.

An actor is not just a synchronization mechanism, it can have functionality of its own. Isolation is only one of the guarantees an actor gives you.

1 Like

The key distinction is: You can only exchange Sendable (SE-302) items with an actor. Actor can't expose mutable objects to the outside world, even asynchronously.

In order to protect the mutable state of an object graph with an actor, you need to create a full set of API on the actor to manipulate the object graph. Basically the actor becomes a façade for the object graph.

1 Like
Terms of Service

Privacy Policy

Cookie Policy