Rust里面的PartialEq Eq PartialOrd Ord
在 Rust 中, PartialEq 和 PartialOrd主要用于处理比较操作。
简单用一句话概括它们的区别:
PartialEq用于判断两个值是否相等(控制==和!=运算符)。PartialOrd用于判断两个值的大小关系(控制<、>、<=、>=运算符)。
想要彻底搞懂它们,真正需要理解的是它们名字里的 Partial(偏/部分) 是什么意思。。
1. 为什么叫 "Partial" (偏/部分)?
在数学里,比较分为“全(Total)”和“偏(Partial)”。
- 全(Total): 集合里的任意两个元素,必定能比出个结果。
- 偏(Partial): 集合里可能存在某些“奇葩”元素,它们和谁都没法比(甚至和自己都没法比)。
在 Rust 中,这个“奇葩”元素的经典代表就是浮点数里的 NaN(Not a Number)。
按照 IEEE 754 标准,NaN 代表一个未定义或不可表示的浮点数(比如 0.0 / 0.0 的结果)。它的反直觉特性是:
NaN == NaN的结果是 falseNaN < 1.0的结果是 falseNaN > 1.0的结果是 false
因为这种“无法比较”的特殊情况存在,Rust 不能盲目地假设所有类型都能百分百比较大小和相等。因此诞生了带有 Partial 前缀的 Trait。
2. PartialEq (部分相等)
只要你为一个类型实现了 PartialEq,你就可以对它使用 == 和 !=。
特点:
它允许类型中的某些值 a,出现 a == a 为 false 的情况(比如上面说的 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 (全序)
如果你保证你的类型里任意两个值都能比出大小(比如整数),你可以实现 Ord。Ord 的 cmp 方法返回的直接是 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 安全性和严谨性的一大体现!