Skip to content

G.TRA.01 在实现相等性、排序和散列类 trait 时,应保证一致性

【级别】 要求

【描述】

在手动实现 HashPartialEq/ Eq 时必须要满足等式 (k1 == k2) == (hash(k1) == hash(k2)),即当 k1k2 相等时, hash(k1) 也应该和 hash(k2) 相等。

在手动实现 PartialOrdOrd 时则必须要满足等式 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)
  } 
}