ПрограммированиеРазработчик библиотек

Что такое PartialEq и Eq в Rust, какой контракт они обеспечивают при сравнении значений, и почему важно соблюсти строгость равенства в пользовательских типах?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса

В Rust трейты PartialEq и Eq возникают из философии type safety — все фундаментальные для коллекций операции (HashMap, HashSet) основаны на строгом определении равенства. Трейты определяют, могут ли объекты двух типов сравниваться с помощью == и обязуется ли тип реализовать "полное" равенство (Eq) или "частичное" (PartialEq).

Проблема

Неправильная реализация PartialEq/Eq приводит к некорректной работе коллекций, где нарушаются базовые математические свойства: сравнение с самим собой, симметрия, транзитивность. Кроме того, некорректный Eq может привести к тому, что объекты "теряются" в коллекциях или возникают неожиданные дубликаты.

Решение

PartialEq реализуется для типов, у которых сравнение возможно не для всех значений (например, f32, где NaN != NaN). Eq предназначен только для "строгих" (тотальных) отношений. Пользовательские типы должны реализовывать сравнение в строгом соответствии с этими контрактами.

Пример кода:

#[derive(PartialEq, Eq)] struct Point { x: i32, y: i32, } let p1 = Point { x: 1, y: 2 }; let p2 = Point { x: 1, y: 2 }; assert!(p1 == p2); // true

Ключевые особенности:

  • PartialEq допускает "частичное" сравнение — могут быть значения, которые не сравниваются корректно (например, NaN).
  • Eq требует полного отношения: x == x должно быть всегда true; все свойства отношения должны соблюдаться.
  • Правильная реализация этих трейтов критична для коллекций и функций стандартной библиотеки.

Вопросы с подвохом.

Почему для типа f32 стандартная библиотека реализует только PartialEq, но не Eq?

Потому что по IEEE-754 NaN != NaN, т.е. свойство x == x для всех x не соблюдается, а это необходимое свойство для Eq.

Обязательно ли реализовывать Eq явно, если реализован PartialEq вручную?

Да, если тип поддерживает полное равенство (x == x всегда true), необходимо вручную реализовать и Eq, иначе некоторые структуры откажутся компилироваться с вашим типом.

Может ли пользователь реализовать PartialEq "несимметрично" (x == y, но y != x)?

Технически да, но это приводит к некорректной работе коллекций и считается багом.

Типовые ошибки и анти-паттерны

  • Игнорировать необходимые свойства сравнения, делая реализацию PartialEq "с изъяном".
  • Не реализовать Eq для тех типов, где это возможно, и ломать работу HashMap/HashSet.
  • Реализовать симметрию "по-своему", нарушая спецификацию.

Пример из жизни

** Негативный кейс

Пользователь реализовал PartialEq для типа, сравнивающего только по одному полю, остальные игнорируются. В результате HashSet "теряет" дубликаты.

Плюсы:

  • Простая реализация, можно быстро сравнивать

Минусы:

  • Ложные совпадения, потеря уникальности

** Позитивный кейс

Использует #[derive(PartialEq, Eq)] либо реализует собственную версию сравнения, учитывающую всю бизнес-логику, гарантируя требуемые свойства.

Плюсы:

  • Корректно работают коллекции, строгое поведение

Минусы:

  • Требует внимательного и вдумчивого подхода к проектированию