要在像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的字段。结果,无法找到所需的键导致用户从缓存中“泄漏”:元素被认为是相同的,尽管它们是不同的。
故事
在一个用于几何对象的开源库中,在
Hash和Eq中使用了浮点数比较(f64)。因此,由于NaN和-0.0/+0.0比较的特性,标准容器出现了错误,有时无法在集合中找到现有对象。
故事
在一个fintech项目中,在重构时,使用了
#[derive(Hash, Eq, PartialEq)]的自动派生,针对一个具有私有字段的结构,改变了它们的声明顺序。结果导致最终的哈希发生了变化,破坏了缓存的工作,并在重启后造成了数据丢失。