编程后端和数据开发者

揭示如何在Rust中为自定义类型实现Hash和Eq。在实现过程中可能会出现哪些错误,以及这些错误在HashMap/HashSet中的使用为何至关重要?

用 Hintsage AI 助手通过面试

回答

要在像HashMapHashSet这样的集合中使用自定义结构,必须实现特质EqHash(通常还需要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的字段。结果,无法找到所需的键导致用户从缓存中“泄漏”:元素被认为是相同的,尽管它们是不同的。


故事

在一个用于几何对象的开源库中,在HashEq中使用了浮点数比较(f64)。因此,由于NaN和-0.0/+0.0比较的特性,标准容器出现了错误,有时无法在集合中找到现有对象。


故事

在一个fintech项目中,在重构时,使用了#[derive(Hash, Eq, PartialEq)]的自动派生,针对一个具有私有字段的结构,改变了它们的声明顺序。结果导致最终的哈希发生了变化,破坏了缓存的工作,并在重启后造成了数据丢失。