При реализации Hashable какое отношение между равенством значений и результатом hash(into:) обязательно сохранить?
Если два значения равны согласно ==, их hash(into:) обязан добавлять в хешер эквивалентный набор значимых данных и давать совместимый результат хеширования. Обратное неверно: одинаковый хеш не означает равенство значений, потому что коллизии допустимы.
Hashable нужен для эффективного хранения и поиска значений в хеш-таблицах, используемых, например, коллекциями Set и ключами Dictionary. Такая структура сначала вычисляет хеш, чтобы быстро выбрать область поиска, а затем использует равенство для окончательной проверки.
Контракт хеширования возник как компромисс между скоростью и точностью: хеш позволяет быстро сузить поиск, но не обязан однозначно идентифицировать значение.
Основной риск появляется, когда == и hash(into:) используют разные свойства. Тогда равные значения могут попасть в разные хеш-области, и Set или Dictionary начнут вести себя некорректно: поиск существующего элемента может не найти его, а словарь может не получить значение по равному ключу.
Обратная ситуация — разные значения с одинаковым хешем — допустима. Она лишь ухудшает производительность из-за большего числа сравнений.
Hashable наследует требования Equatable, поэтому тип должен определять равенство и хеширование согласованно. Из правила следует только одна обязательная импликация: если a == b, то хеши a и b должны быть совместимы. Требование hash(a) != hash(b) при a != b отсутствует.
В hash(into:) нужно добавлять те же семантически значимые свойства, которые участвуют в ==, в согласованном порядке. Свойства, игнорируемые равенством, нельзя использовать для различения хеша.
Здесь name не участвует ни в равенстве, ни в хешировании, поэтому пользователи с одним id считаются одним ключом. Hasher может использовать случайное начальное состояние, поэтому числовой хеш не следует сохранять между запусками приложения или использовать как постоянный идентификатор.
Особенно опасны изменяемые значения, уже находящиеся в Set или используемые ключами Dictionary. Если изменить поле, влияющее на равенство или хеш, элемент логически окажется в другой хеш-области, пока коллекция всё ещё хранит его по старой позиции.
В приложении User идентифицируется по стабильному серверному id, а отображаемое имя может изменяться. Вариант с включением id и name в равенство кажется естественным, но приведёт к тому, что переименование пользователя изменит его идентичность как ключа. Вариант с хешированием только id без изменения == нарушит контракт, если равенство всё ещё учитывает имя.
Корректное решение — явно определить идентичность через id и использовать его же в == и hash(into:). Если идентификатор может изменяться, безопаснее не изменять такой объект внутри Set или не использовать изменяемую модель непосредственно ключом словаря. Это сохраняет корректность поиска ценой необходимости создавать новый ключ или обновлять коллекцию явно.
Нет. Разные значения могут иметь одинаковый хеш — это коллизия, предусмотренная моделью хеш-таблиц. Обязательное требование направлено только от равенства к совместимому хешу; уникальность хеша для каждого значения не гарантируется.
Нет. Совпадение хешей — лишь повод выполнить дополнительное сравнение через ==. Использование одного хеша как доказательства равенства приводит к ошибкам при коллизиях.
Коллекция размещает ключ с учётом его хеша в момент вставки. Если затем изменится свойство, участвующее в == или hash(into:), новый поиск вычислит другую позицию, хотя объект физически остался в прежней. Поэтому ключи должны быть логически неизменяемыми на время хранения в хеш-коллекции.