Swift Hashable

I already covered making a custom Swift type Equatable and Comparable which allows us to test if an Array contains an instance of our type or to sort the array amongst other things. What if we want to store our type in a Set or Dictionary?

Update 2 April 2018: Starting with Swift 4.1 you can have the compiler automatically synthesize Hashable conformance for your type. See How To Get Equatable And Hashable For Free.

Making Our Custom Type Hashable

To use a type in a Set or Dictionary it has to be Hashable which means providing a hashValue property. Your type must also be Equatable which I covered in Swift Equatable and Comparable. To recap our Swift Country type looks like this:

struct Country {
  let name: String
  let capital: String
  var visited: Bool
}

extension Country: Equatable {
  static func == (lhs: Country, rhs: Country) -> Bool {
    return lhs.name == rhs.name &&
      lhs.capital == rhs.capital &&
      lhs.visited == rhs.visited
  }
}

The hashValue property is an integer that must be the same for any two instances of the type that are equal. Swift standard library types such as String, Integer, Bool are all Hashable.

// hash values can vary
let hello  = "hello"
let world = "world"
hello.hashValue                // 4799432177974197528
"\(hello) \(world)".hashValue  // 3658945855109305670
"hello world".hashValue        // 3658945855109305670

Starting with Swift 4.1 the easiest way to make our type hashable is to have the compiler automatically synthesise conformance for us (see How To Get Equatable And Hashable For Free).

As long as all of the properties of our struct are themselves hashable it's enough to declare conformance and the compiler does the rest:

extension Country: Hashable {}

let uk = Country(name: "United Kingdom", capital: "London", visited: true)
uk.hashValue // -8344213515631337230 (actual value will vary)

To understand what the compiler is doing we can manually implement the hash(into:) method:

extension Country: Hashable {
  func hash(into hasher: inout Hasher) {
    hasher.combine(name)
    hasher.combine(capital)
    hasher.combine(visited)
  }
}

We just feed each of the essential components of our type into the combine function of the hasher provided by the standard library. Note that these are the same components we use when implementing Equatable.

We can now use Country in a Set or Dictionary:

let destinations: Set = [belgium,canada,brazil]
let europe: Set = [belgium,france,uk]
destinations.intersection(europe)  // belgium

let counts = [uk: 1000, france: 2000]
counts[uk]  // 1000

Notes:

  • Do not assume that two instances of a type with the same hash value are equal. Depending on how we compute the hash value we can get collisions where two different instances share the same hash value. The Hashable protocol only needs the reverse - two equal instances have the same hash value.

  • The hash value of a Bool from the Swift Standard Library happens to be either 0 (false) or 1 (true) - but don't depend on those values.

  • There is no guarantee that a hash value will be the same for different executions of your code.

Further Reading