I'm curious why hasher.combine can't implicitly open the any Hashable in this code?
struct Key: Hashable {
let id: any Hashable
init<ID: Hashable>(id: ID) {
self.id = id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id) // shouldn't any Hashable get unboxed here?
// id.hash(into: &hasher) // this works as it should
}
static func == (lhs: Key, rhs: Key) -> Bool {
lhs.id.isEqual(to: rhs.id)
}
}
tera
2
You can make this non generic:
init(id: any Hashable) {
self.id = id
}
This:
hasher.combine(id)
doesn't work because the protocol (in this case Hashable) doesn't conform to itself.
For the same reason this doesn't compile either:
lhs.id.isEqual(to: rhs.id)
FWIW, the end result of these statements is the same:
hasher.combine(id) // if it worked
id.hash(into: &hasher)
According to SE-352, combine should unbox the Hashable. In fact, I can get it to work by pulling a piece of code from SE-352:
func testOpenSimple(hasher: inout Hasher, id: any Hashable) {
hasher.combine(id) // this unboxes any Hashable correctly
}
struct Key: Hashable {
let id: any Hashable
init<ID: Hashable>(id: ID) {
self.id = id
}
func hash(into hasher: inout Hasher) {
testOpenSimple(hasher: &hasher, id: id)
}
static func == (lhs: Key, rhs: Key) -> Bool {
lhs.id.isEqual(to: rhs.id)
}
}
extension Equatable {
func isEqual<E: Equatable>(to rhs: E) -> Bool {
if let rhs = rhs as? Self {
self == rhs
} else {
false
}
}
}
Key(id: a).hashValue
(note that the init is generic simply because it was pulled from other code
)
Also, I included isEqual so you can see why that works.
tera
4
I see. BTW, do you except to see any difference in the result between this:
func hash(into hasher: inout Hasher) {
testOpenSimple(hasher: &hasher, id: id)
}
and this:
func hash(into hasher: inout Hasher) {
id.hash(into: &hasher)
}
Thank you. Beware of the gotcha of your implementation:
class C: Equatable {
static func == (lhs: C, rhs: C) -> Bool { true }
}
class D: C {}
C().isEqual(to: D()) // true
D().isEqual(to: C()) // false
No, they both produce the same hash (as I would expect) though internally, one will call through the existential and the other will use the value directly.
And thanks for the reminder about the isEqual. I probably should have an assert in case I ever use it with a class hierarchy.