ProgrammingBackend and Data Developer

Explain how Hash and Eq are implemented for custom types in Rust. What mistakes can be made during their implementation and why are they critical when using HashMap/HashSet?

Pass interviews with Hintsage AI assistant

Answer

To use custom structures in collections like HashMap or HashSet, it is necessary to implement the traits Eq and Hash (often also PartialEq). The implementation of these traits defines how objects are compared to each other and how their hash is computed.

Importantly: if two objects are equal (a == b), then their hash values must match (hash(a) == hash(b)). Otherwise, containers that depend on hashes will not function correctly (it will be impossible to find or remove an element).

Example implementation:

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); } }

Trick question

Can you implement Hash for a structure where some fields participate in equality comparison (Eq/PartialEq), and some only in hashing?

Answer: No, this will lead to an invariant violation: if two elements are considered equal, their hash must be the same. All fields involved in PartialEq/Eq must also be considered in Hash. Ignoring "non-comparable" fields is safe only if they do not participate in the comparison logic.

impl Hash for MyType { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); // if only self.x is used in Eq } }

Examples of real mistakes due to ignorance of the nuances of the topic


Story

In a large server project, a structure for a key was used in HashMap, but one field participating in PartialEq was forgotten in hashing. As a result, the inability to find the required key led to a "leak" of users from the cache: elements were considered identical, although they were different.


Story

In an open-source library for geometric objects, floating-point comparison (f64) was used in Hash and Eq. As a result, due to the peculiarities of comparing NaN and -0.0/+0.0, standard containers worked incorrectly, sometimes not finding an existing object in the collection.


Story

In one fintech project, during refactoring, auto-derivation #[derive(Hash, Eq, PartialEq)] was used for a structure with private fields, changing the order of their declaration. As a result, the final hash changed, which broke the caching mechanism and caused data loss after a restart.