How can I make Optionals Hashable in Xcode 9.4

Viewed 559

In Xcode 10, the Swift compiler is smart enough to:

  1. Treat Optionals that wrap Hashable values as being Hashable.

  2. Xcode >=9.4 will also treat structs that contain all Hashable properties as also being Hashable.

Curiously, Xcode 10 treats Optionals as Hashable even if the build language is Swift 4, or even Swift 3.

Take the following code for example:

struct Foo: Hashable {
    var string1: String?
    var string2: String?
}

That code compiles under Xcode 10, and using the hashValue of a Foo object works as expected, even if the build language is Swift 3!

However, in Xcode 9.4, the Foo struct is not automatically Hashable unless you make the properties non-optional.

This code compiles and works as you would want it to in Xcode 9.4/Swift 4.1:

struct Foo: Hashable {
    var string1: String
    var string2: String
}

How can I make optionals Hashable in Xcode 9? I could certainly implement Hashable conformance for my struct myself, but creating good hashes for multi-part structures is actually kinda tricky, and I would like to not have to worry about it.

I also can't think of a straightforward way to make an Optional Hashable. (again, this is only an issue in Xcode < Xcode 10. My employer has not yet moved to Xcode 10, so I need a solution that works for Xcode 9.4)

1 Answers

Swift 4.2 defines the conditional Optional: Hashable conformance in Optional.swift as

extension Optional: Hashable where Wrapped: Hashable {
  //  ...
  public func hash(into hasher: inout Hasher) {
    switch self {
    case .none:
      hasher.combine(0 as UInt8)
    case .some(let wrapped):
      hasher.combine(1 as UInt8)
      hasher.combine(wrapped)
    }
  }
}

In particular

  • .none has the same hash value as the integer zero, independent of the type, and
  • the hash value .some(wrapped) is obtained by mixing the hash value of wrapped with that of the integer one.

Swift 4.1 (which comes with Xcode 9.4) has already the necessary tools to implement a similar method:

  • Conditional conformance, i.e. we can define an extension

    extension Optional: Hashable where Wrapped: Hashable
    
  • Automatic synthesis of Hashable for types if all members are Hashable.

Here is a possible implementation:

extension Optional: Hashable where Wrapped: Hashable {
    struct Combiner: Hashable {
        let left: Int
        let right: Wrapped
    }

    public var hashValue: Int {
        switch self {
        case .none:
            return 0.hashValue
        case .some(let wrapped):
            return Combiner(left: 1, right: wrapped).hashValue
        }
    }
}

Example:

struct Foo: Hashable {
    var string1: String?
    var string2: String?
}

let foo = Foo(string1: "1", string2: "2")
print(foo.hashValue) // 2171695307022640119

To make this code compile with Swift 4.2 and newer as well, the extension method can be conditionally compiled (compare SE-0212 Compiler Version Directive):

#if swift(>=4.1.50) || (swift(>=3.4) && !swift(>=4.0))
// Code targeting the Swift 4.2 compiler and above:
// Conditional conformance `Optional: Hashable` defined in the standard library.
#elseif swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
// Code targeting the Swift 4.1 compiler and above:

extension Optional: Hashable where Wrapped: Hashable {
    // ...
}

#endif
Related