"Actors are reference types, but why classes?"

tl;dr: the only thing that makes an actor (class) different from a class is that an actor (class) protects its state from data races, so they should be exactly that: nothing less, nothing more.

The proposal states that actors are fundamentally different from classes with this reasoning:

“Actor classes” (as proposed) are a fundamentally different type from classes already: Actor classes cannot subclass normal classes, and normal classes cannot subclass actor classes.

The subclassing rule is not some fundamental difference in types; it is a direct consequence of protecting actor state. If an actor class were to inherit from a non-actor class, it would mean that the whole of the actor class's state has not been protected against data races, because one could easily have data races through the superclass. That would undercut the actor model.

The other direction has the same issue: a non-actor class C inheriting from an actor class A could introduce data races in any instance members it adds, meaning that one could no longer rely on data-race freedom when working with values of (static) type A. That, too, would undercut the actor model.

But the subclassing rule need not be so strict. For example, we could allow an actor class to inherit from a non-actor class if that non-actor class promised to prevent data races via some other mechanism, e.g., by promising that all its state was manually synchronized via something like @actorIndependent(unsafe). Indeed, this is why the NSObject carve-out is legitimate: NSObject has no instance members, so there's nothing to data-race on.

So, this subclassing restriction isn't a rigid dividing line, it's a consequence of the one thing actor classes do differently from classes, which is to protect their state from data races.
Abstractly, I can understand the desire to eliminate subclassing. Lots of complexities in the Swift language come with subclassing, particularly around initializers and protocol conformances. It's temping to cut those inconvenient parts out of actors, because it makes actors simpler---but it doesn't actually manifest in any actual improvements for actors. It also doesn't make Swift simpler, overall, because classes and subclassing don't go away. Rather, we've only made it harder to move classes over to actor classes because we've made some arbitrary restrictions on actors because we decided we don't like certain features. I feel like the suggestion to not have subclassing is guided more by what @austintatious calls the "'structs+protocols+generics'" than by its benefits to actors themselves.

Eliminating support for static stored properties is the other restriction mentioned. Yes, static stored properties aren't part of the actor's isolated state, so we could remove the notion entirely---regardless of whether actors are classes or not----but I don't see the point: there are ways to make them useful (with @actorIndependent or global actors or whatever).

I'm going to pointedly ignore the section about implementation cost in the compiler, because it's not informing the decision here and is more likely to confuse than illuminate.

Instead, we should talk about overall language complexity. Having actors be a small "delta" on top of classes makes them easier to teach and learn. "An actor class is a class that protects its state from data races" sums up the feature. Where actor classes behave differently from classes---disallowing access to stored instance properties except on self, disallowing subclassing with non-actor classes, disallowing synchronous calls to instance methods, etc.---one can explain all these as consequences of that one initial definition.

I can see us having the debate about the spelling of actor or actor class, but making actors a new, distinct nominal type would add complexity, confusion, and unnecessary barriers to the language.

Doug

16 Likes