G.TRA.01 在实现相等性、排序和散列类 trait 时,应保证一致性
【级别】 要求
【描述】
在手动实现 Hash
和 PartialEq/ Eq
时必须要满足等式 (k1 == k2) == (hash(k1) == hash(k2))
,即当 k1
和 k2
相等时, hash(k1)
也应该和 hash(k2)
相等。
在手动实现 PartialOrd
和 Ord
时则必须要满足等式 k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
。
若类型实现了 Borrow
trait,也应保证上述等式对借用后的类型同样成立,即:
hash(k1) == hash(k2) 等效于 hash(k1.borrow()) == hash(k2.borrow())
,
且 k1.borrow().cmp(&k2.borrow()) 等效于 k1.borrow().partial_cmp(&k2.borrow()).unwrap()
;
通常情况下,可以通过 derive
属性宏来自动实现这些 traits。但相等性,排序和散列在不同应用场景 可能存在不同的定义,又或是因为依赖的某种类型未实现其中一个 trait 而不能使用 derive
时,就需 要开发者手动实现以上 traits,这时就要注意保持一致性。
一旦不一致,可能导致出现非预期的函数行为。
【反例】
Rust
// 不符合,雇员(`Employee`)类型包括 uid 及 name,通过宏自动实现的 `Hash` 会同时考虑所有成 员变量,
// 但是手动实现的 `PartialEq` trait 仅考虑到了 uid,这将会导致结果不一致。
// 因为当两个雇员(假设为 a 和 b)的 name 不同,但 uid 相同时 , `a == b` 为 true, 但`hash(a)` 不等于 `hash(b)`
#[derive(Hash, Eq)]
struct Employee {
uid: u32,
name: String,
}
impl PartialEq for Employee {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
}
}
【正例】
Rust
// 符合,都使用 `derive` 自动实现
#[derive(Hash, PartialEq, Eq)]
struct Employee {
uid: u32,
name: String,
}
或
use std::hash::{Hash, Hasher};
#[derive(Eq)]
struct Employee {
uid: u32,
name: String,
}
// 符合,都使用手动实现的方式且保证一致性, // 与 `hash` 一样都使用 uid 作为唯一条件
impl PartialEq for Employee {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
}
}
impl Hash for Employee {
fn hash<H: Hasher>(&self, state: &mut H) {
self.uid.hash(state)
}
}
【反例】
Rust
#[derive(Ord, PartialEq, Eq)]
struct Employee {
uid: u32,
name: String,
}
// 不符合 , `Ord` 和 `PartialOrd` 的实现不一致
impl PartialOrd for Employee {
fn partial_cmp(&self, other: &Employee) -> Option<Ordering> {
Some(self.uid.cmp(&other.uid))
}
}
【正例】
Rust
// 符合 , 都使用 `derive` 自动实现
#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Employee {
uid: u32,
name: String,
}
#[derive(PartialEq, Eq)]
struct Employee {
uid: u32,
name: String,
}
// 符合 , 都使用手动实现的方式且保证一致性
impl PartialOrd for Employee {
fn partial_cmp(&self, other: &Employee) -> Option<Ordering> {
Some(self.uid.cmp(&other.uid))
}
}
impl Ord for Employee {
fn cmp(&self, other: &Self) -> Ordering {
self.uid.cmp(&other.uid)
}
}