'Avoid Equatable and Hashable boilerplate, Swift 4.2

On project we are using classes for model's layer and because of that I have to write code like this:

// MARK: - Hashable
extension Player: Hashable {
    static func == (lhs: Player, rhs: Player) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(self.name)
    }
}

Can this boilerplate can be somehow avoided? Is it possible to implement that Equatable compare by .hashValue by default? Thanks.



Solution 1:[1]

You could write your custom template via Stencil markup language and autogenerate the code using the Sourcery library.

Or use the existing solutions (AutoEquatable, AutoHashable Sourcery templates).

And also you could write something like this:

protocol IHash: class { }

extension IHash where Self: Hashable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
}

class User: IHash, Hashable {
    var name: String = ""

    func hash(into hasher: inout Hasher) {
        hasher.combine(self.name)
    }
}

It will help you to avoid duplication in different classes.

Solution 2:[2]

This is wrong, and it would make no sense that the compiler synthesizes it automatically:

static func == (lhs: Player, rhs: Player) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

Identical objects must have the same hash value, but not the other way around: Distinct objects can have the same hash value.

Concretely, in your example: The name is a string and there are infinitely many different strings, but only 264 different hash values. So there must be two different strings with the same hash value.

If all stored properties are Hashable then the compiler can synthesize the conformance for you completely. For example

struct Player : Equatable, Hashable {
    let name: String
    var score: Int
}

Here two players are “identical” if they have the same name and the same score.

If there are non-hashable properties, or if you want to customize the concept of identity then you have to override == and hash(into) accordingly. The hash function should use the same properties which determine the identity in ==. For example

struct Player : Equatable, Hashable {
    let name: String
    var score: Int

    static func == (lhs: Player, rhs: Player) -> Bool {
        return lhs.name == rhs.name
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(self.name)
    }
}

Now two players are “identical” if they have the same name.

Solution 3:[3]

Using the following is wrong and the reason is Hash collisions

static func == (lhs: Player, rhs: Player) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

Hash was invented as a 'fast' way to compare objects. If 2 objects have different hashes it is Guaranteed that they are different.

But if they have same hashes we are not sure if they are the same.

So static func == (lhs: Player, rhs: Player) -> Bool is needed as a fallback method to use when hashes are the same in order to make sure objects are indeed the same.

So, if you use .hashvalue as implementation then sometimes you will get a false result because of the hash collision.

To accelerate the implementation of the Hashable protocol I recommend you start using sourcery to your project.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Kwnstantinos Nikoloutsos