How are Int and String accepted as AnyHashable?

前端 未结 3 1127
面向向阳花
面向向阳花 2021-01-05 01:18

How come I can do this?

    var dict = [AnyHashable : Int]()
    dict[NSObject()] = 1
    dict[\"\"] = 2

This implies that NSObject

3条回答
  •  独厮守ぢ
    2021-01-05 01:57

    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. 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(%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(_ 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.

提交回复
热议问题