Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How are Int and String accepted as AnyHashable?

Tags:

swift

swift3

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?

like image 474
Remover Avatar asked Feb 03 '17 09:02

Remover


Video Answer


2 Answers

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.

like image 39
OOPer Avatar answered Oct 22 '22 21:10

OOPer


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.

like image 114
Hamish Avatar answered Oct 22 '22 21:10

Hamish