ProgrammationDéveloppeur de bibliothèques

Qu'est-ce que PartialEq et Eq en Rust, quel contrat assurent-ils lors de la comparaison des valeurs, et pourquoi est-il important de respecter la rigueur de l'égalité dans les types personnalisés ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

En Rust, les traits PartialEq et Eq proviennent de la philosophie de la sécurité des types — toutes les opérations fondamentales pour les collections (HashMap, HashSet) reposent sur une définition stricte de l'égalité. Les traits définissent si des objets de deux types peuvent être comparés à l'aide de == et obligent le type à réaliser une "égalité complète" (Eq) ou "partielle" (PartialEq).

Problème

Une mauvaise implémentation de PartialEq/Eq conduit à un fonctionnement incorrect des collections, où les propriétés mathématiques de base sont violées : la comparaison avec soi-même, la symétrie, la transitivité. De plus, un Eq incorrect peut entraîner la "perte" d'objets dans les collections ou la génération de doublons inattendus.

Solution

PartialEq est implémenté pour les types pour lesquels la comparaison n'est pas possible pour toutes les valeurs (par exemple, f32, où NaN != NaN). Eq est réservé uniquement aux relations "strictes" (totales). Les types personnalisés doivent implémenter la comparaison en stricte accordance avec ces contrats.

Exemple de code :

#[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); // vrai

Caractéristiques clés :

  • PartialEq permet une "comparaison partielle" — il peut exister des valeurs qui ne se comparent pas correctement (par exemple, NaN).
  • Eq exige une relation complète : x == x doit toujours être vrai ; toutes les propriétés de la relation doivent être respectées.
  • Une bonne implémentation de ces traits est cruciale pour les collections et les fonctions de la bibliothèque standard.

Questions pièges.

Pourquoi pour le type f32 la bibliothèque standard n’implémente-t-elle que PartialEq, mais pas Eq ?

Parce qu'en vertu de la norme IEEE-754, NaN != NaN, c'est-à-dire que la propriété x == x pour tous les x n'est pas respectée, et c'est une propriété nécessaire pour Eq.

Est-il obligatoire d'implémenter Eq explicitement si PartialEq est implémenté manuellement ?

Oui, si le type supporte l'égalité complète (x == x est toujours vrai), il est nécessaire d'implémenter manuellement Eq, sinon certaines structures ne compileront pas avec votre type.

Un utilisateur peut-il implémenter PartialEq de manière "non symétrique" (x == y, mais y != x) ?

Techniquement oui, mais cela entraîne un fonctionnement incorrect des collections et est considéré comme un bug.

Erreurs typiques et anti-patrons

  • Ignorer les propriétés nécessaires de la comparaison, en réalisant une implémentation de PartialEq "défectueuse".
  • Ne pas implémenter Eq pour les types où cela est possible, ce qui rompt le fonctionnement de HashMap/HashSet.
  • Implémenter la symétrie "à sa manière", violant la spécification.

Exemple de la vie réelle

** Cas négatif

L'utilisateur a implémenté PartialEq pour un type comparant uniquement un champ, les autres étant ignorés. En conséquence, HashSet "perd" les doublons.

Avantages:

  • Implémentation simple, on peut comparer rapidement

Inconvénients:

  • Correspondances fausses, perte d'unicité

** Cas positif

Utilise #[derive(PartialEq, Eq)] ou implémente sa propre version de la comparaison, tenant compte de toute la logique métier, garantissant les propriétés requises.

Avantages :

  • Fonctionnement correct des collections, comportement strict

Inconvénients :

  • Nécessite une approche attentive et réfléchie en matière de conception