本文将围绕对象:PartialEq和Eq,以及PartialOrd和Ord,即四个Rust中重点的Compare Trait进行讨论并解释其中的细节,内容涵盖理论以及代码实现。
在正式介绍PartialEq和Eq、以及PartialOrd和Ord之前,本文会首先介绍它们所遵循的数学理论,也就是相等关系。
文章主要分三大部分,第一部分是第1节,讨论的是数学中的相等关系;第二部分是第2~5节,主要讨论PartialEq和Eq;第三部分是第6节,主要讨论PartialOrd和Ord。内容描述可能具有先后顺序,建议按章节顺序阅读。
本文内容来自作者个人的学习成果总结及整理,可能会存在因个人水平导致的表述错误,欢迎并感谢读者指正!
在初中数学中,会介绍到什么是相等关系(也叫等价关系),相等关系是一种基本的二元关系,它描述了两个对象之间的相等性质。它必须满足如下三个性质:
a=a
;a=b
,则有b=a
;a=b
和b=c
,则有a=c
;也就是说,满足这三个性质才叫满足(完全)相等关系。这很容易理解,就不过多解释。
对于简单的整数类型、字符串类型,我们可以说它们具有完全相等关系,因为它们可以全方位比较(包含两个维度,第一个是类型空间中的任意值,第二个是每个值的任意成员属性), 但是对于某些类型就不行了,这些类型总是不满足其中一个维度
。下面一起来看看:
以字符串为例,全方位比较的是它的每个字节值以及整个字符串的长度。
在浮点数类型中有个特殊的值是NaN(Not-a-number),这个值与任何值都不等(包括自己),它直接违背了自反性。这个时候,我们就需要为浮点数定义一种部分相等关系,这主要是为了比较非NaN浮点数。
NaN定义于IEEE 754-2008标准的5.2节“特殊值”(Special Values)中,除了NaN,另外两个特殊值是正无穷大(+infinity)、负无穷大(-infinity),不过这两个值满足自反性。
除了浮点数类型,数学中还有其他类型也不具有通常意义上的全等关系,比如集合类型、函数类型。
假设有集合A={1,2,3}、B={1,3,2},那么此时A和B是相等还是不相等呢?这就需要在不同角度去看待,当我们只关注集合中是否包含相同的元素时, 可以说它们相等,当我们还要严格要求元素顺序一致时,它们就不相等。
在实际应用中,由我们定义(Impl)了一种集合中的特殊相等关系,称为"集合的相等",这个特殊关系(实现逻辑)中,我们只要求两个集合的元素相同,不要求其他。
首先从浮点数的NaN角度来看函数,假设有函数A=f(x)、B=f(y),若x=y,那显然A的值也等于B,但是如果存在一个参数z是无意义的呢,意思是f(z)是无结果的或结果非法,那么此时可以说f(z)等于自身吗?
那显然是不行的。这个例子和浮点数的例子是一个意思。
然后从集合类型的角度再来看一次函数,假设有函数A=f(x)、B=g(x),注意是两个不同的函数,当二者给定一个相同输入x产生相同结果时,此时f(x)和g(x)是相等还是不等呢?
与集合类似,实际应用中,这里也是由我们定义(Impl)了一种函数中的特殊相等关系,称为函数的相等。这个特殊关系(实现逻辑)中,我们只要求两个函数执行结果的值相同,不要求函数执行过程相同。
部分相等是全相等关系的子集,也就是说,如果两个元素具有相等关系,那它们之间也一定有部分相等关系。这在编程语言中的实现也是同样遵循的规则。
数学中定义了(全)相等关系(等价关系)的三大性质,分别是自反性、对称性和传递性;但某些数据类型中的值或属性违背了三大性质,就不能叫做满足全相等关系, 此时只能为该类型实现部分相等关系。
在部分相等关系中,用于比较的值也是满足三大性质的,因为此时我们排除了那些特殊值。另外,部分相等是全相等关系的子集。
数学是一门研究数据、空间和变化的庞大学科,它提供了一种严谨的描述和处理问题的方式,而编程则是将问题的解决方法转化为计算机程序的过程,可以说,数学是问题的理论形式, 编程则是问题的代码形式,编程解决问题的依据来自数学。
所以说,编程语言的设计中也是大量运用了数学概念与模型的,本文关注的相等关系就是一个例子。
在Rust库中的PartialEq
的注释文档中提到了partial equivalence relations 即部分相等关系这一概念,并且同样使用了浮点数的特殊值NaN来举例说明。
Eq
的注释文档则是提到了equivalence relations,并且明确说明了,对于满足Eq
trait的类型,是一定满足相等关系的三大性质的。
Rust中的PartialEq的命名明确地表达了它的含义,但如果我们忘记了数学中的相等关系,就肯定会对此感到疑惑。先来看看它的定义:
pub trait PartialEq<Rhs: ?Sized = Self> {
fn eq(&self, other: &Rhs) -> bool;
fn ne(&self, other: &Rhs) -> bool {
!self.eq(other)
}
}
在这个定义中,可以得到三个基本信息:
?Size
,即包括动态大小类型(DST)和固定大小类型(ST)类型(Rhs是主类型用来比较的类型);Self
(和主类型一致),但也可以是其他类型,也就是说,实践中你甚至可以将i32
与struct进行比较,只要实现了对应的PartialEq
;Rust中的lhs和rhs指的是,“left-hand side”(左手边) 和 “right-hand side”(右手边)的参数。
这个比较简单,PartialEq和Eq一致,拥有的eq和ne方法分别对应==
和!=
两个操作符。Rust的大部分基本类型如整型、浮点数、字符串等都实现了PartialEq, 所以它们可以使用==
和!=
进行相等性比较。
英文描述为Derivable,即通过derive
宏可以为自定义复合类型(struct/enum/union类型)自动实现PartialEq,用法如下:
#[derive(PartialEq)]
struct Book {
name: String,
}
#[derive(PartialEq)]
enum BookFormat { Paperback, Hardback, Ebook }
#[derive(PartialEq)]
union T {
a: u32,
b: f32,
c: f64,
}
需要注意的是,可派生的前提是这个复合类型下的所有成员字段都是支持PartialEq的,下面的代码说明了这种情况:
// #[derive(PartialEq)] // 取消注释即可编译通过
enum BookFormat { Paperback, Hardback, Ebook }
// 无法编译!!!
#[derive(PartialEq)]
struct Book {
name: String,
format: BookFormat, // 未实现PartialEq
}
扩展:使用
cargo expand
命令可以打印出宏为类型实现的PartialEq代码。
以上一段代码为例,我们假设BookFormat
是引用其他crate下的代码,无法为其添加derive语句(不能修改它),此时就需要手动为Book
手动实现PartialEq,代码如下:
enum BookFormat { Paperback, Hardback, Ebook }
struct Book {
name: String,
format: BookFormat,
}
// 要求只要name相等则Book相等(假设format无法进行相等比较)
impl PartialEq for Book {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
fn main() {
let bk = Book { name: "x".to_string(), format: BookFormat::Ebook };
let bk2 = Book { name: "x".to_string(), format: BookFormat::Paperback };
assert!(bk == bk2); // 因为Book实现了PartialEq,所以可以比较相等性
}
根据上面的trait定义中,我们知道了只要在实现PartialEq时关联不同类型的Rhs参数,就能比较不同类型的相等性。示例代码如下:
#[derive(PartialEq)]
enum WheelBrand {
Bmw,
Benz,
Michelin,
}
struct Car {
brand: WheelBrand,
price: i32,
}
impl PartialEq<WheelBrand> for Car {
fn eq(&self, other: &WheelBrand) -> bool {
self.brand == *other
}
}
fn main() {
let car = Car { brand: WheelBrand::Benz, price: 10000 };
let wheel = WheelBrand::Benz;
// 比较 struct和enum
assert!(car == wheel);
// assert!(wheel == car); // 无法反过来比较
}
需要注意的是,代码片段中仅实现了Car与Wheel的相等性比较,若要反过来比较,还得提供反向的实现,如下:
impl PartialEq<Car> for WheelBrand {
fn eq(&self, other: &Car) -> bool {
*self == other.brand
}
}
上文说过,Rust的基本类型都实现了PartialEq,那具体是怎么实现的呢?是为每个类型都写一套impl代码吗?代码在哪呢?
如果你使用IDE,可以通过在任意位置按住ctrl键(视IDE而定)点击代码中的PartialEq
以打开其在标准库中的代码文件cmp.rs
,相对路径是RUST_LIB_DIR/core/src/cmp.rs
。
在该文件中可以找到如下宏代码:
mod impls {
// ...
macro_rules! partial_eq_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const PartialEq for $t {
#[inline]
fn eq(&self, other: &$t) -> bool { (*self) == (*other) }
#[inline]
fn ne(&self, other: &$t) -> bool { (*self) != (*other) }
}
)*)
}
partial_eq_impl! {
bool char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64
}
// ...
}
这里使用了Rust强大的宏特性(此处使用的是声明宏,还算简单),来为Rust的众多基本类型快速实现了PartialEq trait。如果你还不了解宏,可以暂且理解其是一种编写重复模式代码规则的编程特性,它可以减少大量重复代码。
理解了PartialEq,那Eq理解起来就非常简单了,本节的内容主体与PartialEq基本一致,所以相对简明。
如下:
pub trait Eq: PartialEq<Self> {
fn assert_receiver_is_total_eq(&self) {}
}
根据代码可以得到两个重要信息:
assert_receiver_is_total_eq()
,并且有默认实现;第一个,既然Eq继承自PartialEq,说明想要实现Eq,必先实现PartialEq。第二个是这个assert_receiver_is_total_eq()
方法了,简单来说,它是被derive语法内部使用的,用来断言类型的每个属性都实现了Eq特性,对于使用者的我们来说, 其实不用过多关注。
与PartialEq无差别,略。
与PartialEq的使用相似,只是要注意派生时,由于继承关系,Eq和PartialEq必须同时存在。
#[derive(PartialEq, Eq)] // 顺序无关
struct Book {
name: String,
}
直接看代码:
enum BookFormat { Paperback, Hardback, Ebook }
struct Book {
name: String,
format: BookFormat,
}
// 要求只要name相等则Book相等(假设format无法进行相等比较)
impl PartialEq for Book {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Book {}
fn main() {
let bk = Book { name: "x".to_string(), format: BookFormat::Ebook };
let bk2 = Book { name: "x".to_string(), format: BookFormat::Paperback };
assert!(bk == bk2);
}
需要注意的是,必须先实现PartialEq,再实现Eq。另外,这里能看出的是,在比较相等性方面,Eq和PartialEq都是使用==
和!=
操作符,无差别感知。
与PartialEq无差别,略。
与PartialEq一样,在相对路径为RUST_LIB_DIR/core/src/cmp.rs
的文件中,存在如下宏代码:
mod impls {
/*
... (先实现PartialEq)
*/
// 再实现Eq
macro_rules! eq_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
impl Eq for $t {}
)*)
}
eq_impl! { () bool char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
}
目前在标准库中,笔者只发现有浮点数是只实现了PartialEq的(以及包含浮点数的复合类型),下面是浮点数的测试代码:
fn main() {
fn check_eq_impl<I: Eq>(typ: I) {}
// check_eq_impl(0.1f32); // 编译错误
// check_eq_impl(0.1f64); // 编译错误
let nan = f32::NAN;
let infinity = f32::INFINITY;
let neg_infinity = f32::NEG_INFINITY;
assert_ne!(nan, nan); // 不等!
assert_eq!(infinity, infinity); // 相等!
assert_eq!(neg_infinity, neg_infinity); // 相等!
}
很多时候,当我们谈到PartialEq和Eq时,PartialOrd和Ord总是不能脱离的话题,因为它们都是一种二元比较关系,前两者是相等性比较,后两者是有序性(也可称大小性)比较。 前两者使用的操作符是==
和!=
,后两者使用的操作符是>
、=
、<
,没错,PartialOrd和Ord的比较结果是包含等于的,然后我们可以基于这个有序关系来对数据进行排序(sort)。
重点:有序性包含相等性。
与PartialEq存在的原因一样,PartialOrd的存在的理由也是因为有一些类型是不具有有序性关系的(无法比较),比如浮点数、Bool、Option、函数、闭包等类型。
PartialEq和Eq、PartialOrd和Ord共同描述了Rust中任意类型的二元比较关系,包含相等性、有序性。 所以在上文中,你可能也观察到PartialOrd和Ord的定义也位于cmp.rs
文件中。
我们可以将PartialOrd和Ord直译为偏序和全序关系,因为这确实是它们要表达的含义。偏序和全序的概念来自离散数学,下文详解。
PartialOrd和Ord也是满足一定的基本性质的,PartialOrd满足:
a、b,则a。且>
和==
也是一样的;
a,则b>a
;
Ord基于PartialOrd,自然遵循传递性和对立性,另外对于任意两个元素,还满足如下性质:
>
或==
或<
其中的一个关系;// 二元关系定义(<,==,>)
pub enum Ordering {
Less = -1,
Equal = 0,
Greater = 1,
}
pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
fn lt(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Less))
}
fn le(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Less | Equal))
}
fn gt(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Greater))
}
fn ge(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Greater | Equal))
}
}
基本信息:
partial_cmp()
方法用于主类型和可以是其他类型的参数比较,返回的Option
,表示两者关系可以是无法比较的(None),那么这里我们就可以联想到OrdOrdering
(因为具有全序的类型不会存在无法比较的情况);<
, <=
, >
, >=
,即实现了PartialOrd的类型可以使用这些操作符进行比较;除此之外,由于继承了PartialEq,所以还允许使用==
,!=
;请再次记住,不管是PartialOrd还是Ord,都包含相等关系。
pub trait Ord: Eq + PartialOrd<Self> {
// 方法1
fn cmp(&self, other: &Self) -> Ordering;
// 方法2
fn max(self, other: Self) -> Self
where
Self: Sized,
Self: ~ const Destruct,
{
// HACK(fee1-dead): go back to using `self.max_by(other, Ord::cmp)`
// when trait methods are allowed to be used when a const closure is
// expected.
match self.cmp(&other) {
Ordering::Less | Ordering::Equal => other,
Ordering::Greater => self,
}
}
// 方法3
fn min(self, other: Self) -> Self
where
Self: Sized,
Self: ~ const Destruct,
{
// HACK(fee1-dead): go back to using `self.min_by(other, Ord::cmp)`
// when trait methods are allowed to be used when a const closure is
// expected.
match self.cmp(&other) {
Ordering::Less | Ordering::Equal => self,
Ordering::Greater => other,
}
}
// 方法4
fn clamp(self, min: Self, max: Self) -> Self
where
Self: Sized,
Self: ~ const Destruct,
Self: ~ const PartialOrd,
{
assert!(min <= max);
if self < min {
min
} else if self > max {
max
} else {
self
}
}
}
基本信息:
cmp
方法用于比较self与参数other
的二元关系,返回Ordering
类型(区别于PartialOrd.partial_cmp()返回的Option
);min/max()
方法以返回self与参数other
之间的较小值/较大值;clamp()
方法返回输入的参数区间内的值;<
, <=
, >
, >=
, ==
, !=
;对
Self: ~ const Destruct
的解释:位于where后即是类型约束,这里约束了Self
类型必须是实现了Destruct
trait的一个指向常量的裸指针。
全序和偏序的概念(来自离散数学)
- 全序:即全序关系,自然也是一种二元关系。全序是指,集合中的任两个元素之间都可以比较的关系。比如实数中的任两个数都可以比较大小,那么“大小”就是实数集的一个全序关系。
- 偏序:集合中只有部分元素之间可以比较的关系。比如复数集中并不是所有的数都可以比较大小,那么“大小”就是复数集的一个偏序关系。
- 显然,全序关系必是偏序关系。反之不成立。
PartialOrd和Ord也是可以使用derive
宏进行自动实现的,代码如下:
#[derive(PartialOrd, PartialEq)]
struct Book {
name: String,
}
#[derive(PartialOrd, PartialEq)]
enum BookFormat { Paperback, Hardback, Ebook }
这里有几点需要注意:
union
类型派生;下面使用代码对第2,3点举例说明:
#[derive(PartialOrd, PartialEq)]
struct Book {
name: String,
}
assert!(Book { name: "a".to_string() } < Book { name: "b".to_string() });
assert!(Book { name: "b".to_string() } < Book { name: "c".to_string() });
// 字典序中,数字<字母(按ASCII编码排序)
assert!(Book { name: "1".to_string() } < Book { name: "2".to_string() });
assert!(Book { name: "2".to_string() } < Book { name: "a".to_string() });
// 字典序中,如果比较多字节字符,则先转为其Unicode的十六进制形式,然后逐字节比较
// 比如 中文 "曜" 和 "耀" 的Unicode编码分别为0x66DC和0x8000,所以前者小于后者
assert_eq!("曜", "\u{66dc}");
assert_eq!("耀", "\u{8000}");
assert!(Book { name: "曜".to_string() } < Book { name: "耀".to_string() });
#[derive(PartialOrd, PartialEq)]
enum BookFormat {
Paperback,
// 1
Hardback,
// 2
Ebook, // 3
}
assert!(BookFormat::Paperback < BookFormat::Hardback);
assert!(BookFormat::Hardback < BookFormat::Ebook);
#[derive(PartialOrd, PartialEq)]
enum BookFormat2 {
// 手动指定枚举的值,则可以改变它们的大小顺序
Paperback = 3,
Hardback = 2,
Ebook = 1,
}
assert!(BookFormat2::Paperback > BookFormat2::Hardback);
assert!(BookFormat2::Hardback > BookFormat2::Ebook);
对于字典序比较规则,还有一些特殊情况,如下:
#[derive(Ord, Eq, PartialOrd, PartialEq)]
struct Book {
name: String,
}
#[derive(Ord, Eq, PartialOrd, PartialEq)]
enum BookFormat {
Paperback,
Hardback,
Ebook,
}
这里只需注意一点,那就是由于继承关系,Ord需要和Eq, PartialOrd, PartialEq同时派生。另外,根据前面所提到的,PartialOrd和Ord都支持>=
, <=
,这个要记得;
// 注意这里测试对象是Book3,不要为成员字段format即BookFormat3派生任何trait,模拟实际项目中无法修改成员字段特性的情况
enum BookFormat3 {
Paperback,
Hardback,
Ebook,
}
struct Book3 {
name: String,
format: BookFormat3,
}
// -- 先得实现 PartialEq
impl PartialEq<Self> for Book3 {
// tips:这里可以将省略
fn eq(&self, other: &Self) -> bool {
self.name == other.name
// 这里假设format字段不要求比较
}
}
// -- 才能实现 PartialOrd
impl PartialOrd for Book3 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// 直接调用name(String)的比较方法,如果成员字段也没有实现PartialOrd,那就得先为成员实现,这类情况很少
self.name.partial_cmp(&other.name)
}
}
// 测试对象:Book3
// - 这里同样没有使用任何derive,全手动实现,由于继承关系,需要实现四个trait
// - 注意:若存在任一成员字段(这里指 format字段)未实现PartialEq/Eq/PartialOrd,都是无法为Book3派生Ord的(派生时不会解析下面的手动impl)
enum BookFormat3 {
Paperback,
Hardback,
Ebook,
}
struct Book3 {
name: String,
format: BookFormat3,
}
// -- 先实现 PartialEq
impl PartialEq for Book3 {
fn eq(&self, other: &Book3) -> bool {
self.name == other.name
// 这里假设format字段不要求比较
}
}
// -- 再实现 Eq
impl Eq for Book3 {}
// -- 再实现 Ord
impl Ord for Book3 {
fn cmp(&self, other: &Book3) -> Ordering {
// 直接调用name(String)的cmp方法(当需要实现Ord时,成员字段一般都实现了Ord,可直接调用其cmp方法)
self.name.cmp(&other.name)
}
}
// -- 最后实现 PartialOrd
impl PartialOrd for Book3 {
fn partial_cmp(&self, other: &Book3) -> Option<Ordering> {
// 直接调用上面实现的cmp方法
Some(self.cmp(&other))
}
}
阅读此代码需要注意几点:
cmp()
;这一节不贴代码了,留给读者去实现。具体实现手法可参考前面3.5节或4.5节中的内容。
我们以前面介绍过的同样的方式找到cmp.rs
中PartialOrd的实现宏,代码如下:
mod impls {
// ... 前面是PartialEq和Eq的宏实现
macro_rules! partial_ord_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const PartialOrd for $t {
#[inline]
fn partial_cmp(&self, other: &$t) -> Option<Ordering> {
// 注意看,此处是根据两个比较结果来得到最终结果,本质上是要求比较的值满足对立性(浮点数NaN不满足,所以返回None)
match (*self <= *other, *self >= *other) {
(false, false) => None,
(false, true) => Some(Greater),
(true, false) => Some(Less),
(true, true) => Some(Equal),
}
}
#[inline]
fn lt(&self, other: &$t) -> bool { (*self) < (*other) }
#[inline]
fn le(&self, other: &$t) -> bool { (*self) <= (*other) }
#[inline]
fn ge(&self, other: &$t) -> bool { (*self) >= (*other) }
#[inline]
fn gt(&self, other: &$t) -> bool { (*self) > (*other) }
}
)*)
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const PartialOrd for () {
#[inline]
fn partial_cmp(&self, _: &()) -> Option<Ordering> {
Some(Equal)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const PartialOrd for bool {
#[inline]
fn partial_cmp(&self, other: &bool) -> Option<Ordering> {
Some(self.cmp(other))
}
}
partial_ord_impl! { f32 f64 }
}
这里要注意一下几点:
partial_ord_impl!
是通过两个比较结果来得到最终结果(看注释);()
和bool
类型。浮点数类型不必多说,单元类型是一种单值类型用于排序的情况也比较少,为bool类型实现这个trait的原因是:self.cmp()
);这里的
impl const
中的const关键字意味着标记这个trait实现是编译时常量(编译时优化),以保证运行时不会有额外开销。这里是因为fn partial_cmp()
的实现没有修改任何数据才可以加const
,当然还有其他要求例如不能使用动态分配的内存(例如 Box 或 Vec)、不能调用非 const 函数等;
mod impls {
// ... 前面是PartialEq/Eq/PartialOrd的宏实现
macro_rules! ord_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const PartialOrd for $t {
#[inline]
fn partial_cmp(&self, other: &$t) -> Option<Ordering> {
Some(self.cmp(other))
}
#[inline]
fn lt(&self, other: &$t) -> bool { (*self) < (*other) }
#[inline]
fn le(&self, other: &$t) -> bool { (*self) <= (*other) }
#[inline]
fn ge(&self, other: &$t) -> bool { (*self) >= (*other) }
#[inline]
fn gt(&self, other: &$t) -> bool { (*self) > (*other) }
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const Ord for $t {
#[inline]
fn cmp(&self, other: &$t) -> Ordering {
// The order here is important to generate more optimal assembly.
// See for more info.
if *self < *other { Less }
else if *self == *other { Equal }
else { Greater }
}
}
)*)
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const Ord for () {
#[inline]
fn cmp(&self, _other: &()) -> Ordering {
Equal
}
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_cmp", issue = "92391")]
impl const Ord for bool {
#[inline]
fn cmp(&self, other: &bool) -> Ordering {
// Casting to i8's and converting the difference to an Ordering generates
// more optimal assembly.
// See for more info.
match (*self as i8) - (*other as i8) {
-1 => Less,
0 => Equal,
1 => Greater,
// SAFETY: bool as i8 returns 0 or 1, so the difference can't be anything else
_ => unsafe { unreachable_unchecked() },
}
}
}
ord_impl! { char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
}
这里需要了解一下几点:
partial_cmp()
内部调用的是Ord的cmp()
,理由前面讲过;()
和bool类型实现了Ord;char usize u8 u16 ...
实现了Ord;这里指的其他类型是!
、不可变借用类型
、可变借用类型
,具体实现代码就在源码中刚刚看的宏ord_impl!
下方,此处就不再赘述。