16

In my Swift 4.2.1 code I have this enumeration:

enum MyEnum {

    case caseOne(Int)
    case caseTwo(String)
    case caseThree
}

It conforms to Equatable:

extension MyEnum: Equatable {

    static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {

        switch (lhs, rhs) {
        case (.caseOne, .caseOne), (.caseTwo, .caseTwo), (.caseThree, .caseThree):
            return true
        default:
            return false
        }
    }
}

I need to make it conform to Hashable, that's why I added an extension:

extension MyEnum: Hashable {

    var hashValue: Int {

        switch self {
        case .caseOne:
            return 1
        case .caseTwo:
            return 2
        case .caseThree:
            return 3
        }
    }
}

Now I want to migrate to the new API available in Xcode 10. I removed my implementation of hashValue and added the implementation of hash(into:):

extension MyEnum: Hashable {

    func hash(into hasher: inout Hasher) {

        switch self {
        case .caseOne:
            hasher.combine(1)
        case .caseTwo:
            hasher.combine(2)
        case .caseThree:
            hasher.combine(3)
        }
    }
}

Could you please tell me if I switched correctly to the new API? I use this test, it prints true twice if everything works fine:

var testDictionary = [MyEnum: Int]()
testDictionary[.caseOne(100)] = 100
testDictionary[.caseOne(1000)] = 1000
testDictionary[.caseTwo("100")] = 100
testDictionary[.caseTwo("1000")] = 1000
let countCaseOne = testDictionary.reduce(0) {
    if case .caseOne = $1.key {
        return $0 + 1
    }
    return $0
} == 1
print(countCaseOne) // true
let countCaseTwo = testDictionary.reduce(0) {
    if case .caseTwo = $1.key {
        return $0 + 1
    }
    return $0
} == 1
print(countCaseTwo) // true
Roman Podymov
  • 4,168
  • 4
  • 30
  • 57
  • What do you want to know? It looks correct to me. – Sandeep Jan 04 '19 at 11:39
  • @Sandeep In tutorials I've seen something like ```hasher.combine(self.property)``` in the implementation of ```hash(into:)```. I did not find an example with enum. That's why I want to know if my implementation is correct. – Roman Podymov Jan 04 '19 at 11:42
  • 2
    By the way, you dont have to implement the method yourself. The language does it by itself based on your properties. – Sandeep Jan 04 '19 at 11:45

2 Answers2

19

You can use autogenerated Hashable conformance, as proposed in the other answer (under condition your type doesn't contains any date of non-Hashable types).

But that's what you can do in the general case(autogenerated code would probably look like that too):

extension MyEnum: Hashable {

    func hash(into hasher: inout Hasher) {

        switch self {
        case .caseOne(let value):
            hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
        case .caseTwo(let value):
            hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
        case .caseThree:
            // you can `combine` with some `Hashable` constant, but here it's ok just to skip
            break
        }
    }
}
user28434'mstep
  • 6,290
  • 2
  • 20
  • 35
  • Thank you for your answer. But why can I skip `combine` for the `.caseThree`? – Roman Podymov Jan 04 '19 at 12:10
  • 3
    Because `hasher` already has some initial state (let's call it `seed`) and then `combine(:)` performs some function with the `hasher` state(`seed` initially) and then combined value, and result replaces state of the `hasher`. And then state of the `hasher` gets converted to the `hash` function result, which is accessable via `hashValue` property. So, even you won't `combine` at all you will still get a result. Ofc, if you will have multiple `case`s without `associated value` you better to combine it with some `constant`, otherwise hashes of those cases would be identical. – user28434'mstep Jan 04 '19 at 12:22
  • Check my edited answer please, I added `==` to `MyEnum`. – Roman Podymov Jan 04 '19 at 14:51
  • Because I don't need to check the associated values. – Roman Podymov Jan 04 '19 at 16:11
  • 1
    @RomanPodymov, well, if you don't need it for hash too, you will be ok using your `hash(into:)` implementation from the answer. – user28434'mstep Jan 04 '19 at 16:20
  • WARNING: Be really carefull of this; as if `.caseOne` and `.caseTwo` both have an Int as value and are both filled with same contents. They will get the same hashValue; same if you add a caseFour without associated value; So my advise is always atleast add something extra per hashable case such as an static integer that is unique per case. – automaticoo Dec 20 '22 at 14:16
  • @automaticoo in the question `.caseOne` has Int, but `.caseTwo` **has String**. But, yeah, you better add some "tag" for different case in the Hasher. And I mentioned it in the comment above. For cases without associated value, though. But idea stays the same. – user28434'mstep Dec 21 '22 at 15:11
14

There's no need to implement Hashable conformance manually, the compiler can automatically synthesise that for your specific enum (where all cases with associated values have a Hashable associated value). You just need to declare conformance.

enum MyEnum: Hashable {
    case caseOne(Int)
    case caseTwo(String)
    case caseThree
}

// You don't even need to write `: Equatable`, since automatic Hashable conformance takes care of Equatable too, I just left it there for clarity
extension MyEnum: Equatable {
    static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
        switch (lhs, rhs) {
        case (.caseOne, .caseOne), (.caseTwo, .caseTwo), (.caseThree, .caseThree):
            return true
        default:
            return false
        }
    }
}
Dexter
  • 5,666
  • 6
  • 33
  • 45
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • 3
    IFF all the associated values of enum are `Hashable` too (or it has none). – user28434'mstep Jan 04 '19 at 11:50
  • 1
    @user28434 yes, that's why I mentioned that for OP's specific enum, but I'll edit my answer to make it more general. – Dávid Pásztor Jan 04 '19 at 12:17
  • Thank you for your answer. But I forgot to write that `MyEnum` contains implementation for `==`, check my updated answer please. – Roman Podymov Jan 04 '19 at 14:56
  • 1
    @RomanPodymov that doesn't make a difference, you can still use automatic `Hashable` conformance and overwrite the autosynthetised `==` method. Check my updated answer. Btw are you sure you don't care about the equality of the associated values? – Dávid Pásztor Jan 04 '19 at 15:00
  • @DávidPásztor In my case I don't need to check the associated values. And without the implementation of `hash(into:)` the output of my test is `true`/`true`, `true`/`false` or `false`/`true`. – Roman Podymov Jan 04 '19 at 16:04