Thanks Doug, I'm glad to see nonisolated(unsafe)
get subsetted out of this round of the proposal. Much of my feedback from the 5th draft last night stands, including:
-
Actor
protocol should be named AnyActor
for consistency with AnyClass
and other type erased things in Swift.
-
isolated self
doesn't really work for the same reason we don't use mutating self
in structs.
-
nonisolated
still makes the proposal much larger and more complicated than it should be, and is an additive feature that can compose on top of the basic actor model. nonisolated
also undercuts key future directions for actors like Distributed actors.
After sleeping about it, I have a meta concern about your new approach with "Actor protocols". To restate common ground: I think that we generally agree that actors force a dual nature: there is the "outside" and "inside" the actor viewpoint of things, often seen by the "client" and "implementation" logic of the actor. We need to represent this complexity somehow, and your proposal is to model this division in the protocol definitions. There are other proposals, including modeling this as part of the conformance isolated MyProtocol
vs nonisolated MyProtocol
, and modeling this as part of the actor type var x : @sync MyProtocol
, which are all different ways to factor the complexity with different tradeoffs.
After sleeping on it, I have a high level concern about your new approach for two reasons:
-
It is basically putting the complexity into library and API world instead of modeling it in the language/type system. This is a concern to me because the library ecosystem is FAR larger than the language ecosystem, and is the bulk of the complexity that Swift programmers have to learn in practice. Given that this is fundamental to the nature of actors, it seems appropriate to model it in the language, instead of requiring protocols to get duplicated across the library ecosystem.
-
The existing protocols in Swift serve a dual purpose: both composing in implementation behavior as well as providing public interface logic for types (I understand this may be regretted by a few, but it is the undebatable way Swift works and there are no proposals to change this). Breaking this duality for actors seems like it will require duplicating protocols to model this dual nature in the cases where we're composing in public behavior. This is going to drive a lot of boilerplate and Foo
vs FooActor
protocols patterns.
More generally, your proposal describes a new approach, but I haven't seen an argument or rationale for why you think it is better than the other two approaches. Can you share your thoughts and elaborate on that?
I haven't had time to do a detailed read through of your new draft, but I do see a potential problem in a quick skim:
protocol Actor : AnyObject, Sendable { }
protocol DataProcessible: Actor { ... }
Doesn't this imply that existentials of DataProcessible
will be Sendable
and allow clients to poke at sync members? This isn't memory safe.
The solution to this (which seems consistent with your approach) is to untie these protocols from the Actor protocol, and make them be "actor protocol DataProcessible". It is the actor pointers that are sendable, not the existentials of this wierd "actor protocol" thing. Your approach is to provide a "different color to protocols".
Tying this into the AnyActor
protocol is also weird for other reasons as mentioned last night: actors have a dual nature, so calling either one of them "the actor protocol behavior" is biasing to one at the expense of the other. I'd recommend using a word like "within" or "nonmailbox" if you were to go with this approach.
In any case, I appreciate the ongoing iteration on this proposal. It is a lot of work, but this is an essential part of the entire design for Swift, so I'm happy to see the iteration.
-Chris