I'm talking about things like AnyHashable, AnyView, etc.
Ideally we shouldn't need to hand code type erase because they all seem to be very boilerplate. Can the compiler just do it by itself by making the compiler more smart?
I'm talking about things like AnyHashable, AnyView, etc.
Ideally we shouldn't need to hand code type erase because they all seem to be very boilerplate. Can the compiler just do it by itself by making the compiler more smart?
Yes. What you're talking about is called an "existential" (things like Any
and AnyObject
). You can do that with regular protocol types, without needing to box them in an AnyMyProtocol
type:
protocol MyProtocol {
func doSomething() -> Int
}
func myFunction(_ p: MyProtocol) {
let x = p.doSomething()
}
var existential: MyProtocol = //...
Unfortunately, that doesn't currently work with protocols with associated types ("PAT" for short) or that use Self
. Hashable
, Collection
, View
and certain other common protocols have pre-built boxing types which emulate existentials, and as you're aware, you can also build your own. It's a known issue that will hopefully be alleviated one day by enhancing the compiler.
If you want to know more, "generalized existentials" or "PATs" are search terms you might find helpful.
Wait, why is there an AnyHashable
type? Hashable
doesn’t (currently?) have Self
/PAT requirements, right?
Hashable
conforms to Equatable
, which does have a Self
requirement
Also, as I recently discovered, it is not possible to implement AnyHashable
(or AnyEquatable
) in pure Swift.
If a class Super
implements Equatable
, and then it has two different sub-classes Sub1
and Sub2
, then there is no way to implement the ==
operator for AnyEquatable
correctly in pure Swift.
Instances s1: Sub1
and s2: Sub2
which compare equal via Super.==
still need to compare equal as AnyEquatable(s1)
and AnyEquatable(s2)
, but neither Sub1
nor Sub2
can be converted to each other, and there’s no way to find their common ancestor class which conforms to Equatable
and implements ==
.
The standard library type AnyHashable
is written partially in C++ specifically so that it can find that common ancestor implementing Equatable
.
Is Unlock Existential Types for All Protocols "generalized existentials"?
I guess you could technically call it "generalized existentials", as it allows any protocol and protocol composition type to offer an existential type. The generics manifesto specifically says:
"The restrictions on existential types came from an implementation limitation, but it is reasonable to allow a value of protocol type even when the protocol has Self
constraints or associated types"
However, you should keep in mind that the proposal won't enable use of requirements that use associated types. For instance, this will not work:
protocol Identifiable {
associated type ID : Hashable
var id: ID { get }
}
let identifiable: Identifiable = ...
identifiable.id ❌
// The requirement 'id' uses the associated type
// 'ID' which is prohibited; therefore we get an error.
From my findings, the only thing holding this back is Unlock Existential Types for All Protocols. There's simply no way to tell the compiler that some arbitrary superclass type conforms to Equatable without being able to do something like as! Equatable
or if let type = superclassType as? Equatable.Type
. Once we have that, being able to write AnyHashable
or AnyEquatable
in pure Swift will be possible, albeit with a little reflection help.