10

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?

Hamish
  • 78,605
  • 19
  • 187
  • 280
Remover
  • 1,616
  • 1
  • 17
  • 27
  • Look over this, it may help: http://kandelvijaya.com/2016/10/10/swift3-why-anyhashable-how-does-it-work-internally/. I felt that my answer was unhelpful in this situation, so I removed it – Mr. Xcoder Feb 03 '17 at 10:18

3 Answers3

14

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.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 1
    In other words, it's pure magic from the Swift team... thanks for the insight! – Remover Feb 03 '17 at 19:44
  • @Remover Happy to help :) You'll find quite a few examples in the language where the answer for why they're possible is just "compiler magic". Another one that springs to mind is the implicit conversions of arrays from subtyped elements to supertyped elements, which shouldn't be possible due to the invariance of generics, but the compiler will perform the conversion for you behind the scenes (see for example [this Q&A](http://stackoverflow.com/q/37188580/2976878)). – Hamish Feb 03 '17 at 19:53
1

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.

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • wow... interesting... It seems there is a lot to be learned with Swift – Remover Feb 03 '17 at 19:42
  • @Remover, that should be my word. Re-checking [SE-0131](https://github.com/apple/swift-evolution/blob/master/proposals/0131-anyhashable.md) does not say anything. In the non-optimized generated code, Swift uses extension version of `subscript` for `dict`, and a little more complicated in optimized code... Applause to Hamish. – OOPer Feb 03 '17 at 21:09
-1
class SomeClass: NSObject {
   var something: String = "something"
}

var dict = [AnyHashable: Int]()
var object = SomeClass()

dict = ["a": 1, object: 2]

print(dict["a"]) // result: Optional(1)
print(dict[object]) // result: Optional(2)

var object2 = SomeClass()
dict[object2] = 3
print(dict[object2]) // result: Optional(3)
javimuu
  • 1,829
  • 1
  • 18
  • 29