~
user@blog:~/posts/partialeq-eq-partialord-ord.mdx
vim partialeq-eq-partialord-ord.mdx

Rust里面的PartialEq Eq PartialOrd Ord

testuser_01
2 min read
320 words
Tags:
["Rust"]

在 Rust 中, PartialEqPartialOrd主要用于处理比较操作

简单用一句话概括它们的区别:

  • PartialEq 用于判断两个值是否相等(控制 ==!= 运算符)。
  • PartialOrd 用于判断两个值的大小关系(控制 <><=>= 运算符)。

想要彻底搞懂它们,真正需要理解的是它们名字里的 Partial(偏/部分) 是什么意思。。


1. 为什么叫 "Partial" (偏/部分)?

在数学里,比较分为“全(Total)”和“偏(Partial)”。

  • 全(Total): 集合里的任意两个元素,必定能比出个结果。
  • 偏(Partial): 集合里可能存在某些“奇葩”元素,它们和谁都没法比(甚至和自己都没法比)。

在 Rust 中,这个“奇葩”元素的经典代表就是浮点数里的 NaN(Not a Number)

按照 IEEE 754 标准,NaN 代表一个未定义或不可表示的浮点数(比如 0.0 / 0.0 的结果)。它的反直觉特性是:

  • NaN == NaN 的结果是 false
  • NaN < 1.0 的结果是 false
  • NaN > 1.0 的结果是 false

因为这种“无法比较”的特殊情况存在,Rust 不能盲目地假设所有类型都能百分百比较大小和相等。因此诞生了带有 Partial 前缀的 Trait。


2. PartialEq (部分相等)

只要你为一个类型实现了 PartialEq,你就可以对它使用 ==!=

特点:

它允许类型中的某些值 a,出现 a == afalse 的情况(比如上面说的 f32::NAN)。

对应没有 Partial 的兄弟:Eq (全相等)

如果你能向编译器拍胸脯保证:“我这个类型里绝对没有任何奇葩值,a == a 永远为真!”(比如整数 i32),那你就可以在实现 PartialEq 的基础上,再加一个 Eq 标记。

Eq 里面没有任何实际的方法,它只是给编译器的一个承诺。HashMap 的 Key 就强制要求必须实现 Eq(因为如果 key != key,哈希表就乱套了,所以浮点数不能直接作为 HashMap 的 key)。


3. PartialOrd (偏序 / 部分大小关系)

只要你为一个类型实现了 PartialOrd,你就可以对它使用 <><=>=

特点:

因为有些值无法比较大小,所以 PartialOrd 的核心方法 partial_cmp 返回的是一个 Option<Ordering>

  • 如果能比出大小,返回 Some(Less)Some(Equal)Some(Greater)
  • 如果是 NaN 这种没法比的,返回 None

它的依赖关系:

你必须先知道两个东西是否相等,才能比较它们谁大谁小。所以,PartialOrd 强制要求你必须先实现 PartialEq

// 标准库中 PartialOrd 的定义(简化版)
pub trait PartialOrd: PartialEq {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering>;
}

对应没有 Partial 的兄弟:Ord (全序)

如果你保证你的类型里任意两个值都能比出大小(比如整数),你可以实现 OrdOrdcmp 方法返回的直接是 Ordering(不再包装在 Option 里,因为它绝不会返回 None)。


4. 总结对比表

| Trait | 控制的运算符 | 适用场景 | 返回值特征 | 经典类型代表 | | --- | --- | --- | --- | --- | | PartialEq | ==, != | 可能存在 a != a 的类型 | 布尔值 (bool) | f32, f64 (因为有 NaN) | | Eq | 无 (纯标记) | 保证 a == a 永远成立的类型 | 无 | i32, String, bool | | PartialOrd | <, >, <=, >= | 某些值之间无法比较大小的类型 | Option<Ordering> | f32, f64 | | Ord | 无 (辅助排序等) | 任意两个值都能决出胜负的类型 | Ordering | i32, String, char |

日常代码怎么写?

在 99% 的日常开发中,你不需要手写这些比较逻辑。你只需要用 #[derive(...)] 让编译器帮你自动生成即可。编译器会按结构体字段从上到下的顺序自动帮你比较。

// 大多数情况下,普通数据结构直接把这四个全加上就行了
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct User {
    id: u32,
    name: String,
}

// ⚠️ 但是,如果你的结构体里包含了浮点数,你只能 derive Partial 系列!
#[derive(PartialEq, PartialOrd)] // 如果你加上 Eq 或 Ord,编译器会报错
struct Point {
    x: f32,
    y: f32,
}

这其实是 Rust 安全性和严谨性的一大体现!

cd ../ Back to list