Для использования пользовательских структур в коллекциях типа HashMap или HashSet необходимо реализовать трейты Eq и Hash (чаще также PartialEq). Реализация этих трейтов определяет, как объекты сравниваются друг с другом и как вычисляется их хэш.
Важно: если два объекта равны (a == b), то их хэш-значения должны совпадать (hash(a) == hash(b)). В противном случае контейнеры, зависящие от хэшей, будут работать некорректно (будет невозможно найти или удалить элемент).
Пример реализации:
use std::hash::{Hash, Hasher}; #[derive(Eq, PartialEq)] struct Point { x: i32, y: i32, } impl Hash for Point { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); self.y.hash(state); } }
Можно ли реализовать Hash для структуры, в которой часть полей участвует в сравнении на равенство (Eq/PartialEq), а часть — только в хэшировании?
Ответ:
Нет, это приведёт к нарушению инварианта: если два элемента считаются равными, то их хэш обязателен быть одинаковым. Все поля, участвующие в PartialEq/Eq, должны также учитывать в Hash. Игнорировать "несравнимые" поля безопасно только если они не участвуют в логике сравнения.
impl Hash for MyType { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); // если только self.x используется и в Eq } }
История
В крупном серверном проекте структура для ключа использовалась в
HashMap, но в хэшировании забыли одно поле, участвущее вPartialEq. В результате невозможность найти нужный ключ привела к "утечке" пользователей из кэша: элементы считались одинаковыми, хотя были разными.
История
В open source-библиотеке для геометрических объектов в
HashиEqиспользовали сравнение с плавающей точкой (f64). В результате из-за особенностей сравнения NaN и -0.0/+0.0 стандартные контейнеры работали с ошибками, иногда не находя существующий объект в коллекции.
История
В одном fintech проекте при рефакторинге использовали auto-деривацию
#[derive(Hash, Eq, PartialEq)]для структуры с приватными полями, изменив порядок их объявления. В результате изменился итоговый хэш, что сломало работу кэширования и вызвало потери данных после перезапуска.