Published on

PartialEq与Eq:深入理解相等性比较的差异与实现

在Rust的类型系统中,PartialEqEq是两个用于定义相等性关系的核心特质(trait)。它们虽然相关,但在语义和用途上有着重要区别。本文将深入探讨这两种特质的差异、实现方式以及实际应用场景。

1. 基本概念

1.1 PartialEq特质

PartialEq(部分相等)是Rust中定义部分等价关系的特质。它的定义如下:

pub trait PartialEq<Rhs = Self> 
where
    Rhs: ?Sized,
{
    fn eq(&self, other: &Rhs) -> bool;
    
    #[inline]
    fn ne(&self, other: &Rhs) -> bool {
        !self.eq(other)
    }
}

关键特性:

  • 允许类型与自身或其他类型进行比较
  • 不要求满足等价关系的全部数学性质
  • 自动提供!=运算符(通过ne方法)

1.2 Eq特质

Eq(完全相等)是PartialEq的扩展特质,表示完全等价关系:

pub trait Eq: PartialEq<Self> {}

关键特性:

  • 不添加新方法,只是一个标记特质(marker trait)
  • 表示类型满足等价关系的全部数学性质
  • 启用某些需要完全等价关系的API

2. 数学性质对比

性质PartialEqEq
自反性 (a == a)可选必须
对称性 (a==b ⇒ b==a)必须必须
传递性 (a==b ∧ b==c ⇒ a==c)必须必须

3. 何时使用PartialEq vs Eq

3.1 应该使用PartialEq的情况

  1. 浮点数类型f32f64实现了PartialEq但不是Eq,因为NaN != NaN
  2. 部分可比类型:某些类型可能只在特定条件下可比
  3. 跨类型比较:比较不同类型时通常只用PartialEq
#[derive(PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

// Point可以比较但不实现Eq因为包含浮点数

3.2 应该使用Eq的情况

  1. 完全可比较类型:如整数、字符串等
  2. 需要哈希一致性的类型HashMap等集合通常要求Eq
  3. 需要完全等价关系的算法:如排序、去重等
#[derive(PartialEq, Eq)]
struct UserId(u64);

// UserId实现了完全等价关系

4. 实现指南

4.1 手动实现PartialEq

struct Book {
    isbn: String,
    title: String,
}

impl PartialEq for Book {
    fn eq(&self, other: &Self) -> bool {
        self.isbn == other.isbn  // 仅通过ISBN比较
    }
}

4.2 派生实现

Rust可以自动派生这两种特质:

#[derive(PartialEq, Eq)]
struct Color {
    r: u8,
    g: u8,
    b: u8,
}

5. 实际应用差异

5.1 集合操作

let v = vec![1.0, f64::NAN, 2.0];
v.sort(); // 错误!f64没有实现Eq

let ids = vec![UserId(1), UserId(2)]; 
ids.sort(); // 正确,UserId实现了Eq

5.2 泛型约束

fn dedup<T: Eq>(items: &mut Vec<T>) {
    items.dedup(); // 需要完全等价关系
}

fn find_match<T: PartialEq>(haystack: &[T], needle: &T) -> Option<usize> {
    haystack.iter().position(|x| x == needle)
}

6. 常见误区

  1. 认为所有PartialEq类型都应实现Eq:浮点数就是典型反例
  2. 忽略数学性质:错误实现可能导致意外行为
  3. 过度使用派生:有时需要自定义比较逻辑

7. 最佳实践

  1. 优先考虑派生实现
  2. 对于包含浮点数的结构体,谨慎实现Eq
  3. 确保自定义实现满足必要的数学性质
  4. 在文档中说明相等性比较的语义

THE END