Почему Hashable наследует Equatable в Swift
Что проверяют
Понимаете ли вы контракт хеширования, а не просто помните строчку объявления протокола. Правильный ответ объясняет, почему Set и Dictionary без сравнения на равенство работать не смогли бы.
Как отвечать
Хеш нужен, чтобы быстро сузить поиск до корзины, но он не опознаёт элемент. Разрядность хеша конечна, значений больше, коллизии неизбежны, поэтому последним шагом поиска всегда идёт сравнение на равенство. Без == коллекция не отличит «тот же самый элемент» от «другой элемент, попавший в ту же корзину». Отсюда и объявление protocol Hashable: Equatable, это не иерархия ради удобства, а требование контракта.
Дальше назовите сам инвариант: если a == b, то и хеши обязаны совпадать. Обратное неверно, совпадение хешей равенства не означает. Нарушение не даёт ни ошибки компиляции, ни падения: элемент просто перестаёт находиться в Set, а словарь заводит второй ключ, внешне неотличимый от первого.
Про реализацию: если все хранимые свойства структуры уже соответствуют Hashable, компилятор синтезирует и ==, и hash(into:) сам. Писать руками стоит, когда часть полей в тождество не входит, и тогда обе функции пишутся по одному набору полей.
struct User: Hashable {
let id: UUID
var lastSeen: Date
static func == (l: User, r: User) -> Bool { l.id == r.id }
func hash(into hasher: inout Hasher) { hasher.combine(id) }
}Где спотыкаются
Переопределяют только ==, оставляя синтезированный хеш. Равенство считается по идентификатору, а хеш по всем полям, и два равных объекта с разной датой попадают в разные корзины: поиск в множестве промахивается на объектах, которые там лежат.
Второе: сохраняют hashValue в базу, файл или ключ кеша. Хеширование в Swift использует случайную затравку, поэтому значение меняется от запуска к запуску и годится только внутри процесса. Для стабильного ключа нужен собственный идентификатор.
Третье: кладут в Set объект класса и меняют поле, участвующее в хешировании, уже после вставки. Корзина была выбрана при вставке, элемент остаётся в старой, и проверка на вхождение даёт отрицательный ответ для объекта, который лежит в коллекции. Ключи должны быть неизменяемыми.