Sorry for this lengthy post but I got carried away:
I wouldn't necessarily exclude Copyable from having a declaration or being used as an existential (which is what I think you mean by Copyable
oids). While the exact details about what Copyable
really is is left to a forthcoming proposal (along with generics and noncopyable vlues), Copyable
could in some respects be a typealias of Any
. SE-390 only describes what ~Copyable
means for a type in its inheritance clause.
I'd say AnyObject
, Sendable
, and Copyable
are three loosely related concepts in the language, but not fully protocols. What they all share in common with protocols is their ability to constrain a type variable. My interpretation of a protocol, in its purest form, is something that describes the types of a subset of its conformer's non-private members. In this sense, a protocol doesn't tell you about the conformer's in-memory representation (structs and classes can conform) or what private members it has (implementation detail). Conceptually, protocol requiring a private member to exist doesn't make sense, as that member is not going to be accessible via the protocol. An empty protocol doesn't tell you anything about its conforming types; it just creates a new type functionally equivalent to Any
.
Ways AnyObject
isn't quite a protocol: it allows you to assume its values have a uniform representation in memory as a class object. For example, when you dynamically cast a struct value to AnyObject
, it gets boxed up into an implementation-defined class (called SwiftValue
). There's no reason to ever explicitly state conformance to AnyObject
, nor emit "conformances" to it, since it's an aspect of a type that can always be determined solely by looking at the type's interface in a module (in fact, its members are irrelevant, just "is this a class/actor?"). Thus, it's not necessary (and in fact, disallowed) for programmers to explicitly state AnyObject
conformances. Only protocols can inherit from AnyObject
, to make them "class-only" protocols.
Implementation Note
Within the compiler, AnyObject has no declaration and is "built-in" as a specially-marked empty protocol composition (like Any
). Of course, there's no reason it has to be that way. It totally could be re-implemented as a protocol declaration in the stdlib, with some special treatments. But if we were to reimplement it, I think it'd be better phrased as some new concept, like a "layout constraint", that remains a built-in notion.
Sendable
allows you to assume that it's safe to share the value between tasks, but the reasons why it's safe varies depending on the kind of conformer. For example, a class can't be Sendable
if it has a mutable private stored property. So Sendable
is not quite a protocol either. But unlike AnyObject
, a record of whether a specific type can be considered Sendable
is needed because you cannot determine based on a type's module interface alone. I believe this is what led to marker protocols: no stated requirements, thus no runtime metadata, but types must state whether they conformed.
We're still building-out what Copyable
is, but here are my thoughts: For both programmer convenience and backwards compatibility reasons, we have to be able to determine whether a type is Copyable
without having any explicit "conformances" written in a type's interface. So this has flavors of AnyObject
. On the other hand, a type must be able to explicitly opt-out via ~Copyable
, and that opt-out has to simply be recorded in the module. So the opt-out has flavors of Sendable
. In addition, we'd eventually want ~
to work on more things than Copyable
, so that probably blurs the line even further.
But sometimes blurring the lines is OK. I don't think ordinary programmers need to worry about precisely what these things are. They only need to understand how to use them, and "protocol" terminology is a fine starting point.