Previously, we saw how to conform to Hashable to use a custom type as a Key in a Dictionary, but what if we want to use a set of different types as keys in the same dictionary? Let's look at an example.

  protocol DataKey : Hashable {
  }

  class DataController {
    var allResults:[DataKey:String] = [:]
  }

We have a DataKey class, which may have many members not listed here. Perhaps we want to store "results", whatever that is, keyed by these DataKeys. So we write a Dictionary, but we can't just declare the Key type to be Hashable. This comes from Equatable's definition.

  public static func ==(lhs: Self, rhs: Self) -> Bool

Equatable has to know that the types of the left and right hand side of the == are already the same. They do this with the Self type in their definition. This is called a "Self constraint", and is a form of "same-type contraint". It means that the type of the argument is the "same-type" as whatever type confoms the protocol. Using this Equatable (and thus Hashable) protocol alone as a constraint, there's no way to know whether the types are actually the same if == were called inside the Dictionary, because the exact type information is lost at the function call-site when we passed in something knowing only one conformation.

What we need is another type, which can generically wrap a Hashable instance, and then check the dynamic type of both the left and right-hand side of the == operator, and skip call the wrapped member's implementation if they aren't the same.

We're not even going to write code to do that, because Swift provides it biult-in with the AnyHashable type.

  class DataController {
    //AnyHashable wraps DataKey's
    private var allResults:[AnyHashable:String] = [:]

    func operation<Key:DataKey>(key:Key)->String? {
      return allResults[AnyHashable(key)]
    }

    func set<Key:DataKey>(operation:String, key:Key) {
      allResults[AnyHashable(key)] = operation
    }
  }

We'll need to use generic functions to constrain the key types to conform to DataKey, and then wrap it in an AnyHashable. Our original allResults Dictionary uses AnyHashable as its Key type, and we make it private to prevent other developers from accidentally trying to access it with something that's not a DataKey.

  struct ProfileKey : DataKey {
    var username:String
    static func ==(lhs:ProfileKey, rhs:ProfileKey)->Bool {
      return lhs.username == rhs.username
    }
    var hashValue: Int {
      return username.hashValue
    }
  }

  struct PostKey : DataKey {
    var postID:Int
    static func ==(lhs:PostKey, rhs:PostKey)->Bool {
      return lhs.postID == rhs.postID
    }
    var hashValue: Int {
      return postID.hashValue
    }
  }

  let controller = DataController()

So here are two different types, which both conform to DataKey.

  let profile1Key = ProfileKey(username: "benspratling")
  controller.set(result: "Ben Spratling", key: profile1Key)
  let sameProfileKey = ProfileKey(username: "benspratling")
  controller.result(key: sameProfileKey)  //"Ben Spratling"
  let profileKeyForSomeoneElse = ProfileKey(username: "adamdill")
  controller.result(key: profileKeyForSomeoneElse)  //nil

  let postKey0 = PostKey(postID: 0123)
  controller.set(result: "Introducing Swift 3", key: postKey0)
  controller.result(key: PostKey(postID: 0123))  //"Introducing Swift 3"

And now, we're able to store and retrieve values in the same Dictionary using these different structs as keys.