Search code examples
swiftswift3swift4swift-protocolshashable

Is it possible to use a Type as a dictionary key in Swift?


I’m making a Farm where everything that can be grown conforms to Growable protocol. When you plant a plant, you call this func:

myFarm.planting<T: Growable>(qty: Int, of: T.Type) -> Farm

Now I want each instance of Farm to have a dictionary instance var like:

var crops = [Growable.Type: Int]

The problem is, even if I make the Growable protocol inherit Hashable, this does not help the Growable type become Hashable.

In other words, even if I add an extension to Growable like this:

extension Growable {
    static func hashValue {
        // return some hash
    }
}

... still the Growable type is not Hashable, since the Hashable protocol only concerns instances of types but not the types themselves.

Well, normally I would give up and say, “I am stupid, do not attempt this further.”

However this is Swift, so I figure there must be a way to bend the language to my will, whether by making a new StaticHashable protocol and then extending the Dictionary type with a new subscript method accepting this, or by modding Swift’s source code itself and then making a pitch to the Evolution list.

But before I go down either of those paths, I thought it wise to ask you geniuses if there is already a way to do what I want, or whether doing this is incredibly stupid and you will present me with the obviously superior approach that I was unbelievably daft to have somehow missed all along.

Note: my opinion is that Types themselves should be able to statically adhere to protocols whose funcs are not declared as static, since why should the sender of a message care whether the entity that responds is an immortal God or an ephemeral Creature, made in some God’s image?


Solution

  • Is it possible to use a Type as a dictionary key in Swift?

    Well its possible, here is one way:

    protocol Growable { ... }
    
    struct S : Growable { ... }
    class C : Growable { ... }
    
    extension Dictionary where Key : LosslessStringConvertible
    {
       subscript(index: Growable.Type) -> Value?
       {
          get
          {
             return self[String(describing: index) as! Key]
          }
          set(newValue)
          {
             self[String(describing: index) as! Key] = newValue
          }
       }
    }
    
    var d : [String:Int] = [:]
    d[S.self] = 42
    d[C.self] = 24
    print(d)
    

    prints:

    ["C": 24, "S": 42]
    

    If you change the subscript definition to:

    subscript(index: Any.Type) -> Value?
    

    you can of course use any type as a key:

    var d : [String:Int] = [:]
    d[S.self] = 42
    d[C.self] = 24
    d[type(of:d)] = 18
    print(d)
    

    prints:

    ["C": 24, "S": 42, "Dictionary<String, Int>": 18]
    

    I'll leave it up to you to decide whether this is wise, but its clearly possible.

    [Note: you cannot constrain Key to be String hence the use of the protocol LosslessStringConvertible; there might be a better choice, the Swift standard library is a moving target...]

    HTH