How come I can do this?
var dict = [AnyHashable : Int]()
dict[NSObject()] = 1
dict[""] = 2
This implies that NSObject
and String
are somehow a subtype of AnyHashable
but AnyHashable
is a struct
so, how do they allow this?
You can find this code, when you cmd-click on [
or ]
of dict[NSObject()] = 1
in the Swift editor of Xcode (8.2.1, a little different in 8.3 beta):
extension Dictionary where Key : _AnyHashableProtocol {
public subscript(key: _Hashable) -> Value?
public mutating func updateValue<ConcreteKey : Hashable>(_ value: Value, forKey key: ConcreteKey) -> Value?
public mutating func removeValue<ConcreteKey : Hashable>(forKey key: ConcreteKey) -> Value?
}
_AnyHashableProtocol
and _Hashable
are hidden types, so you may need to check the Swift source code. Simply:
_Hashable
is a hidden super-protocol of Hashable
, so, Int
, String
, NSObject
or all other Hashable
types conform to _Hashable
.
_AnyHashableProtocol
is a hidden protocol, where AnyHashable
is the only type which conforms to _AnyHashableProtocol
.
So, the extension above is very similar to this code in Swift 3.1.
extension Dictionary where Key == AnyHashable {
//...
}
You see, when you write such code like this:
var dict = [AnyHashable : Int]()
dict[NSObject()] = 1
dict[""] = 2
You are using the subscript
operator defined in the extension.
Consider that Optional
is an enum
, which is also a value type – and yet you're freely able to convert a String
to an Optional<String>
. The answer is simply that the compiler implicitly performs these conversions for you.
If we look at the SIL emitted for the following code:
let i: AnyHashable = 5
We can see that the compiler inserts a call to _swift_convertToAnyHashable
:
// allocate memory to store i, and get the address.
alloc_global @main.i : Swift.AnyHashable, loc "main.swift":9:5, scope 1 // id: %2
%3 = global_addr @main.i : Swift.AnyHashable : $*AnyHashable, loc "main.swift":9:5, scope 1 // user: %9
// allocate temporary storage for the Int, and intialise it to 5.
%4 = alloc_stack $Int, loc "main.swift":9:22, scope 1 // users: %7, %10, %9
%5 = integer_literal $Builtin.Int64, 5, loc "main.swift":9:22, scope 1 // user: %6
%6 = struct $Int (%5 : $Builtin.Int64), loc "main.swift":9:22, scope 1 // user: %7
store %6 to %4 : $*Int, loc "main.swift":9:22, scope 1 // id: %7
// call _swift_convertToAnyHashable, passing in the address of i to store the result, and the address of the temporary storage for the Int.
// function_ref _swift_convertToAnyHashable
%8 = function_ref @_swift_convertToAnyHashable : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in τ_0_0) -> @out AnyHashable, loc "main.swift":9:22, scope 1 // user: %9
%9 = apply %8<Int>(%3, %4) : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in τ_0_0) -> @out AnyHashable, loc "main.swift":9:22, scope 1
// deallocate temporary storage.
dealloc_stack %4 : $*Int, loc "main.swift":9:22, scope 1 // id: %10
Looking in AnyHashable.swift, we can see the function with the silgen name of _swift_convertToAnyHashable
, which simply invokes AnyHashable
's initialiser.
@_silgen_name("_swift_convertToAnyHashable")
public // COMPILER_INTRINSIC
func _convertToAnyHashable<H : Hashable>(_ value: H) -> AnyHashable {
return AnyHashable(value)
}
Therefore the above code is just equivalent to:
let i = AnyHashable(5)
Although it's curious that the standard library also implements an extension for Dictionary
(which @OOPer shows), allowing for a dictionary with a Key
of type AnyHashable
to be subscripted by any _Hashable
conforming type (I don't believe there are any types that conform to _Hashable
, but not Hashable
).
The subscript itself should work fine without a special overload for _Hashable
keys. Instead the default subscript (which would take an AnyHashable
key) could just be used with the above implicit conversion, as the following example shows:
struct Foo {
subscript(hashable: AnyHashable) -> Any {
return hashable.base
}
}
let f = Foo()
print(f["yo"]) // yo
Edit: In Swift 4, both the aforementioned subscript overload and _Hashable
have been removed from the stdlib by this commit with the description:
We have an implicit conversion to AnyHashable, so there's no need to have the special subscript on Dictionary at all.
Which confirms my suspicion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With