std1.97.1——cmp模块细览

目录

  • [1. PartialEq Trait](#1. PartialEq Trait)
    • [1.1 跨crate注意事项](#1.1 跨crate注意事项)
    • [1.2 可派生](#1.2 可派生)
    • [1.3 如何实现PartialEq](#1.3 如何实现PartialEq)
    • [1.4 如何比较两个不同的类型](#1.4 如何比较两个不同的类型)
    • [1.5 示例](#1.5 示例)
    • [1.6 必需方法](#1.6 必需方法)
    • [1.7 提供方法](#1.7 提供方法)
  • [2. Eq Trait(不是dyn兼容的)](#2. Eq Trait(不是dyn兼容的))
    • [2.1 可派生](#2.1 可派生)
    • [2.2 如何实现Eq](#2.2 如何实现Eq)
  • [3. PartialOrd Trait](#3. PartialOrd Trait)
    • [3.1 跨crate注意事项](#3.1 跨crate注意事项)
    • [3.2 推论](#3.2 推论)
    • [3.3 严格与非严格偏序](#3.3 严格与非严格偏序)
    • [3.4 可派生](#3.4 可派生)
    • [3.5 如何实现PartialOrd](#3.5 如何实现PartialOrd)
    • [3.6 错误实现示例](#3.6 错误实现示例)
    • [3.7 示例](#3.7 示例)
    • [3.8 必需方法](#3.8 必需方法)
    • [3.9 提供方法](#3.9 提供方法)
      • [3.9.1 lt](#3.9.1 lt)
      • [3.9.2 le](#3.9.2 le)
      • [3.9.3 gt](#3.9.3 gt)
      • [3.9.4 ge](#3.9.4 ge)
  • [4. Ord Trait(不是dyn兼容的)](#4. Ord Trait(不是dyn兼容的))
    • [4.1 推论](#4.1 推论)
    • [4.2 可派生](#4.2 可派生)
    • [4.3 字典序比较](#4.3 字典序比较)
    • [4.4 如何实现Ord](#4.4 如何实现Ord)
    • [4.5 错误实现示例](#4.5 错误实现示例)
    • [4.6 必需方法](#4.6 必需方法)
    • [4.7 提供方法](#4.7 提供方法)
      • [4.7.1 max](#4.7.1 max)
      • [4.7.2 min](#4.7.2 min)
      • [4.7.3 clamp](#4.7.3 clamp)
  • [5. Ordering枚举](#5. Ordering枚举)
    • [5.1 变体](#5.1 变体)
    • [5.2 方法](#5.2 方法)
      • [5.2.1 is_eq](#5.2.1 is_eq)
      • [5.2.2 is_ne](#5.2.2 is_ne)
      • [5.2.3 is_lt](#5.2.3 is_lt)
      • [5.2.4 is_gt](#5.2.4 is_gt)
      • [5.2.5 is_le](#5.2.5 is_le)
      • [5.2.6 is_ge](#5.2.6 is_ge)
      • [5.2.7 reverse](#5.2.7 reverse)
      • [5.2.8 then](#5.2.8 then)
      • [5.2.9 then_with](#5.2.9 then_with)
    • [5.3 Trait实现](#5.3 Trait实现)
      • [5.3.1 Clone](#5.3.1 Clone)
      • [5.3.2 Debug(上面的例子给出了Debug打印)](#5.3.2 Debug(上面的例子给出了Debug打印))
      • [5.3.3 Hash](#5.3.3 Hash)
      • [5.3.4 Ord](#5.3.4 Ord)
      • [5.3.5 PartialEq](#5.3.5 PartialEq)
      • [5.3.6 PartialOrd](#5.3.6 PartialOrd)
      • [5.3.7 Copy](#5.3.7 Copy)
      • [5.3.8 Eq](#5.3.8 Eq)
  • [6. Reverse](#6. Reverse)
    • [6.1 示例](#6.1 示例)
    • [6.2 元组字段](#6.2 元组字段)
    • [6.3 Trait实现](#6.3 Trait实现)
      • [6.3.1 Clone](#6.3.1 Clone)
      • [6.3.2 Debug(上面的例子给出了Debug打印)](#6.3.2 Debug(上面的例子给出了Debug打印))
      • [6.3.3 Default](#6.3.3 Default)
      • [6.3.4 Hash](#6.3.4 Hash)
      • [6.3.5 Ord](#6.3.5 Ord)
      • [6.3.6 PartialEq](#6.3.6 PartialEq)
      • [6.3.7 PartialOrd](#6.3.7 PartialOrd)
      • [6.3.8 Copy](#6.3.8 Copy)
      • [6.3.9 Eq](#6.3.9 Eq)
  • [7. 函数](#7. 函数)
    • [7.1 max](#7.1 max)
      • [7.1.1 示例](#7.1.1 示例)
    • [7.2 max_by](#7.2 max_by)
      • [7.2.1 示例](#7.2.1 示例)
    • [7.3 max_by_key](#7.3 max_by_key)
      • [7.3.1 示例](#7.3.1 示例)
    • [7.4 min](#7.4 min)
      • [7.4.1 示例](#7.4.1 示例)
    • [7.5 min_by](#7.5 min_by)
      • [7.5.1 示例](#7.5.1 示例)
    • [7.6 min_by_key](#7.6 min_by_key)
      • [7.6.1 示例](#7.6.1 示例)
  • 参考

  用于比较和排序值的工具集。

1. PartialEq Trait

rust 复制代码
pub trait PartialEq<Rhs = Self>
where
	Rhs: ?Sized,
{
	// 必需方法
	fn eq(&self, other: &Rhs) -> bool;
	// 提供方法
	fn ne(&self, other: &Rhs) -> bool { ... }
}

  不要求Rhs在编译期是已知大小的。使用相等运算符进行比较的trait。

  为类型实现此trait,即可为这些类型提供==和!=运算符。

  x.eq(y)也可以写成x == y,x.ne(y)可以写成x != y。本页文档余下部分使用更易读的中缀写法。

  本trait为不具备完整等价关系的类型提供相等运算符比较。例如,浮点数中NaN != NaN,因此浮点类型实现了PartialEq而未实现Eq。形式化地说,当Rhs == Self时,本trait对应一个偏等价关系。

  实现必须保证eq与ne彼此一致:

  a != b当且仅当!(a == b)。

  ne的默认实现已提供这种一致性,且几乎总是足够。没有非常充分的理由,不应覆盖它。

  如果Self和Rhs还实现了PartialOrd或Ord,它们的方法也必须与PartialEq保持一致。部分trait用派生、部分trait手动实现,很容易在无意间造成它们相互矛盾。

  相等关系==必须满足以下条件(对类型A、B、C的一切a、b、c):

  对称性:若A: PartialEq<B>且B: PartialEq<A>,则a == b推出b == a。

  传递性:若A: PartialEq<B>、B: PartialEq<C>且A: PartialEq<C>,则a == b且b == c推出a == c。

  违反这些要求属于逻辑错误。逻辑错误导致的行为是未指定的,但此trait的使用者必须确保此类逻辑错误不会导致未定义行为。这意味着unsafe代码不得依赖这些方法的正确性。

1.1 跨crate注意事项

  当一个crate为另一个crate的类型实现PartialEq(例如为了让自己的某个类型能与标准库中的类型比较)时,坚守上述要求可能变得棘手。建议是:永远不要为外部类型实现此trait。换言之,这样的crate应当写impl PartialEq<ForeignType> for LocalType。

  这避免了传递链跨越crate边界交错出现的问题:对所有本地类型T,你可以假设不会有其他crate添加允许比较T == U的实现。换言之,若其他crate添加了允许构建更长传递链U1 == ... == T == V1 == ...的实现,那么出现在T右侧的所有类型都必须是定义T的crate已经知晓的类型。这就排除了下游crate通过新增实现把外部类型的比较缝在一起、从而破坏传递性的可能。

  不写这类针对外部类型的实现还能避免前向兼容问题:某个crate新增更多PartialEq实现,可能会导致下游crate构建失败。

1.2 可派生

  此trait可与#derive一起使用。派生于结构体时,所有字段相等则两个实例相等,任一字段不等则不相等。派生于枚举时,两个实例是同一变体且所有字段相等才相等。

1.3 如何实现PartialEq

  只要两本书的ISBN相同,即使装帧格式不同,也视为同一本书:

rust 复制代码
enum BookFormat {
	Paperback,
	Hardback,
	Ebook,
}

struct Book {
	isbn: i32,
	format: BookFormat,
}

impl PartialEq for Book {
	fn eq(&self, other: &Self) -> bool {
		self.isbn == other.isbn
	}
}

fn main() {
	let b1 = Book {
		isbn: 3,
		format: BookFormat::Paperback,
	};
	let b2 = Book {
		isbn: 3,
		format: BookFormat::Ebook,
	};
	let b3 = Book {
		isbn: 10,
		format: BookFormat::Paperback,
	};
	println!("{}", b1 == b2);
	println!("{}", b1 != b3);
}

1.4 如何比较两个不同的类型

  能与哪种类型比较,由PartialEq的类型参数控制。

rust 复制代码
// 派生实现的是<BookFormat> == <BookFormat>的比较
#[derive(PartialEq)]
enum BookFormat {
	Paperback,
	Hardback,
	Ebook,
}

struct Book {
	isbn: i32,
	format: BookFormat,
}

// 实现<Book> == <BookFormat>的比较
impl PartialEq<BookFormat> for Book {
	fn eq(&self, other: &BookFormat) -> bool {
		self.format == *other
	}
}

// 实现<BookFormat> == <Book>的比较
impl PartialEq<Book> for BookFormat {
	fn eq(&self, other: &Book) -> bool {
		*self == other.format
	}
}

fn main() {
	let b1 = Book {
		isbn: 3,
		format: BookFormat::Paperback,
	};
	println!("{}", b1 == BookFormat::Paperback);
	println!("{}", BookFormat::Ebook != b1);
}

  像上面这样忽略结构体部分字段的比较可能很危险,很容易在无意间违反偏等价关系的要求。

rust 复制代码
#[derive(PartialEq)]
enum BookFormat {
	Paperback,
	Hardback,
	Ebook,
}

#[derive(PartialEq)]
struct Book {
	isbn: i32,
	format: BookFormat,
}

impl PartialEq<BookFormat> for Book {
	fn eq(&self, other: &BookFormat) -> bool {
		self.format == *other
	}
}

impl PartialEq<Book> for BookFormat {
	fn eq(&self, other: &Book) -> bool {
		*self == other.format
	}
}

fn main() {
	let b1 = Book {
		isbn: 1,
		format: BookFormat::Paperback,
	};
	let b2 = Book {
		isbn: 2,
		format: BookFormat::Paperback,
	};
	println!("{}", b1 == BookFormat::Paperback);
	println!("{}", BookFormat::Paperback == b2);
	// 按照传递性,下面这条应当成立------但实际不成立
	println!("{}", b1 == b2);
}

1.5 示例

rust 复制代码
fn main() {
	let x: u32 = 0;
	let y: u32 = 1;
	println!("{}", x == y);
	println!("{}", x.eq(&y));
}

1.6 必需方法

  方法签名:

rust 复制代码
fn eq(&self, other: &Rhs) -> bool

  检验self与other的值是否相等,供==使用。

1.7 提供方法

  方法签名:

rust 复制代码
fn ne(&self, other: &Rhs) -> bool

  检验!=。默认实现几乎总是足够的,没有非常充分的理由不应覆盖。

2. Eq Trait(不是dyn兼容的)

rust 复制代码
pub trait Eq: PartialEq {}

  用于与等价关系对应的比较的trait。

  与PartialEq的主要区别在于额外要求了自反性。实现了PartialEq的类型保证对所有的a、b和c:

  对称性:a == b推出b == a。

  传递性:a == b且b == c推出a == c。

  一致性:a != b当且仅当!(a == b)。

  建立在PartialEq之上的Eq还额外推出:

  自反性:a == a。

  这一性质无法由编译器检查,因此Eq是一个没有方法的trait。

  违反这一性质属于逻辑错误。逻辑错误导致的行为是未指定的,但此trait的使用者必须确保此类逻辑错误不会导致未定义行为。这意味着unsafe代码不得依赖这些方法的正确性。

  诸如f32和f64这样的浮点类型只实现了PartialEq而没有实现Eq,因为NaN != NaN。

2.1 可派生

  此trait可与#derive一起使用。派生时,由于Eq没有额外方法,它只是告知编译器:这是一个等价关系,而非偏等价关系。注意,派生策略要求所有字段都是Eq,这并不总是符合需求。

2.2 如何实现Eq

  如果无法使用派生策略,就声明你的类型实现Eq------它没有任何额外方法。

rust 复制代码
enum BookFormat {
    Paperback,
    Hardback,
    Ebook,
}

struct Book {
    isbn: i32,
    format: BookFormat,
}

impl PartialEq for Book {
    fn eq(&self, other: &Self) -> bool {
        self.isbn == other.isbn
    }
}

impl Eq for Book {}

3. PartialOrd Trait

rust 复制代码
pub trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
where
	Rhs: ?Sized,
{
	// 必需方法
	fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
	// 提供方法
	fn lt(&self, other: &Rhs) -> bool { ... }
	fn le(&self, other: &Rhs) -> bool { ... }
	fn gt(&self, other: &Rhs) -> bool { ... }
	fn ge(&self, other: &Rhs) -> bool { ... }
}

  不要求Rhs在编译期是已知大小的。用于构建偏序partial order的类型的trait。

  本trait的lt、le、gt、ge方法可以分别通过<、<=、>、>=运算符来调用。

  只有当你计划只实现PartialOrd而不实现Ord时,才应该把一个类型的比较逻辑放在本trait中。否则,比较逻辑应该放在Ord中,本trait用Some(self.cmp(other))来实现。

  本trait的方法必须彼此一致,并且与PartialEq的方法一致。以下条件必须成立:

  1. a == b当且仅当partial_cmp(a, b) ==Some(Equal)。

  2. a < b当且仅当partial_cmp(a, b) == Some(Less)。

  3. a > b当且仅当partial_cmp(a, b) == Some(Greater)。

  4. a <= b当且仅当a < b || a == b。

  5. a >= b当且仅当a > b || a == b。

  6. a != b当且仅当!(a == b)。

  上述条件2-5由默认实现保证。条件6已由PartialEq保证。

  ;如果Self和Rhs还实现了Ord,那么Ord也必须与partial_cmp保持一致。通过派生一部分trait,手动实现另一部分的方式,很容易不小心让它们变得不一致。

  比较关系必须满足以下条件(对所有类型A、B、C的值a、b、c):

  传递性:若A: PartialOrd<B>、B: PartialOrd<C>且A: PartialOrd<C>,则a < b且b < c推出a < c。对==和>也必须同样成立。

  对偶性:若A: PartialOrd<B>且B: PartialOrd<A>,则a < b当且仅当b > a。

  违反这些要求属于逻辑错误。逻辑错误导致的行为是未指定的,但此trait的使用者必须确保此类逻辑错误不会导致未定义行为。这意味着unsafe代码不得依赖这些方法的正确性。

3.1 跨crate注意事项

  当一个crate为另一个crate的类型实现PartialOrd时(例如为了让自己的某个类型能与标准库中的类型比较),要维持上述要求就可能变得棘手。建议是:永远不要为外部类型实现本trait。换言之,这样的crate应该写impl PartialOrd<ForeignType> for LocalType。

  这样就避免了传递链跨越crate边界所带来的问题:对于所有本地类型T,你可以假定不会有其他crate添加允许比较T < U的实现。换句话说,如果其他crate添加了能构建更长传递链U1 < ... < T < V1 < ...的实现,那么出现在T右侧的所有类型都必须是定义T的那个crate已经知道的类型。这就排除了下游crate添加新的实现、以违反传递性的方式把外部类型之间的比较拼接起来的传递链。

  不写这类面向外部类型的实现,还可以避免兼容性问题:某个crate日后添加更多PartialOrd实现,可能会在无意间导致下游crate构建失败。

3.2 推论

  以下推论由上述要求直接得出:

  <与>的反自反性:!(a < a)、!(a > a)。

  >的传递性:若a > b且b > c,则a > c。

  partial_cmp的对偶性:partial_cmp(a, b) == partial_cmp(b, a).map(Ordering::reverse)。

3.3 严格与非严格偏序

  <与>运算符的行为符合严格偏序。然而,<=与>=并不符合非严格偏序。这是因为从数学上讲,非严格偏序要求自反性,即对每个a都必须有a <= a为真。对实现了PartialOrd的类型来说,这一点并不总是成立,例如:

rust 复制代码
fn main() {
	let a = f64::NAN;
	println!("{}", a <= a);
}

3.4 可派生

  此trait可与#derive一起使用。

  在结构体上派生时,会按结构体成员从上到下的声明顺序生成字典序。

  在枚举上派生时,变体首先按其判别值顺序,其次再按其字段排序。默认情况下,排在最上面的变体判别值最小,最下面的判别值最大。

rust 复制代码
#[derive(PartialEq, PartialOrd)]
enum E {
	Top,
	Bottom,
}

fn main() {
	println!("{}", E::Top < E::Bottom);
}

  但是,手动设置判别值可以覆盖这一默认行为:

rust 复制代码
#[derive(PartialEq, PartialOrd)]
enum E {
	Top = 2,
	Bottom = 1,
}

fn main() {
	println!("{}", E::Bottom < E::Top);
}

3.5 如何实现PartialOrd

  PartialOrd只要求实现partial_cmp方法,其余方法都会由默认实现生成。

  不过,对于不存在全序total order的类型,仍然可以单独实现其他方法。例如,对浮点数而言,NaN < 0 == false且NaN >= 0 == false。

  PartialOrd要求你的类型实现PartialEq。

  如果你的类型是Ord,可以通过cmp来实现partial_cmp。

rust 复制代码
use std::cmp::Ordering;

struct Person {
	id: u32,
	name: String,
	height: u32,
}

impl PartialOrd for Person {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

impl Ord for Person {
	fn cmp(&self, other: &Self) -> Ordering {
		self.height.cmp(&other.height)
	}
}

impl PartialEq for Person {
	fn eq(&self, other: &Self) -> bool {
		self.height == other.height
	}
}

impl Eq for Person {}

  在你的类型字段上使用partial_cmp也可能很有用。下面是一个Person类的例子,它有一个浮点类型的height字段,且该字段是唯一用于排序的字段。

rust 复制代码
use std::cmp::Ordering;

struct Person {
	id: u32,
	name: String,
	height: f64,
}

impl PartialOrd for Person {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		self.height.partial_cmp(&other.height)
	}
}

impl PartialEq for Person {
	fn eq(&self, other: &Self) -> bool {
		self.height == other.height
	}
}

3.6 错误实现示例

rust 复制代码
use std::cmp::Ordering;

#[derive(Debug, PartialEq)]
struct Character {
	health: u32,
	experience: u32,
}

impl PartialOrd for Character {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.health.cmp(&other.health))
	}
}

fn main() {
	let a = Character {
		health: 10,
		experience: 5,
	};
	let b = Character {
		health: 10,
		experience: 77,
	};
	// 错误:PartialEq与PartialOrd彼此不一致
	println!("{}", a.partial_cmp(&b).unwrap() == Ordering::Equal);
	println!("{}", a == b);
}

3.7 示例

rust 复制代码
fn main() {
	let x: u32 = 0;
	let y: u32 = 1;
	println!("{}", x < y);
	println!("{}", x.lt(&y));
}

3.8 必需方法

  方法签名:

rust 复制代码
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>

  此方法在self与other之间存在顺序关系时,返回该顺序关系。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	let result = 1.0.partial_cmp(&2.0);
	println!("{}", result == Some(Ordering::Less));
	let result = 1.0.partial_cmp(&1.0);
	println!("{}", result == Some(Ordering::Equal));
	let result = 2.0.partial_cmp(&1.0);
	println!("{}", result == Some(Ordering::Greater));
}

  无法比较时:

rust 复制代码
fn main() {
	let result = f64::NAN.partial_cmp(&1.0);
	println!("{}", result == None);
}

3.9 提供方法

3.9.1 lt

  方法签名:

rust 复制代码
fn lt(&self, other: &Rhs) -> bool

  测试小于(针对self与other),供<运算符使用。

rust 复制代码
fn main() {
	println!("{}", 1.0 < 1.0);
	println!("{}", 1.0 < 2.0);
	println!("{}", 2.0 < 1.0);
}

3.9.2 le

  方法签名:

rust 复制代码
fn le(&self, other: &Rhs) -> bool

  测试小于或等于(针对self与other),供<=运算符使用。

rust 复制代码
fn main() {
	println!("{}", 1.0 <= 1.0);
	println!("{}", 1.0 <= 2.0);
	println!("{}", 2.0 <= 1.0);
}

3.9.3 gt

  方法签名:

rust 复制代码
fn gt(&self, other: &Rhs) -> bool

  测试大于(针对self与other),供>运算符使用。

rust 复制代码
fn main() {
	println!("{}", 1.0 > 1.0);
	println!("{}", 1.0 > 2.0);
	println!("{}", 2.0 > 1.0);
}

3.9.4 ge

  方法签名:

rust 复制代码
fn ge(&self, other: &Rhs) -> bool

  测试大于或等于(针对self与other),供>=运算符使用。

rust 复制代码
fn main() {
	println!("{}", 1.0 >= 1.0);
	println!("{}", 1.0 >= 2.0);
	println!("{}", 2.0 >= 1.0);
}

4. Ord Trait(不是dyn兼容的)

rust 复制代码
pub trait Ord: Eq + PartialOrd {
	// 必需方法
	fn cmp(&self, other: &Self) -> Ordering;
	// 提供方法
	fn max(self, other: Self) -> Self
	where Self: Sized,
	{ ... }
	fn min(self, other: Self) -> Self
	where Self: Sized,
	{ ... }
	fn clamp(self, min: Self, max: Self) -> Self
	where Self: Sized,
	{ ... }
}

  用于构建全序total order的类型的trait。

  实现必须与PartialOrd的实现保持一致,并确保max、min、clamp与cmp一致:

  partial_cmp(a, b) == Some(cmp(a, b))。

  max(a, b) == max_by(a, b, cmp)(由默认实现确保)。

  min(a, b) == min_by(a, b, cmp)(由默认实现确保)。

  对a.clamp(min, max),参见该方法(由默认实现确保)。

  违反这些要求属于逻辑错误。逻辑错误导致的未定义行为是未指定的,但此trait的使用者必须确保此类逻辑错误不会导致未定义行为。这意味着unsafe代码不得依赖这些方法的正确性。

4.1 推论

  由上述要求以及PartialOrd的要求可以得出,对所有a、b、c:

  a < b、a == b、a > b三者恰有一个为真。

  且<是传递的:a < b且b < c推出a < c。对==和>也必须同样成立。

  从数学上讲,<运算符定义了一个严格弱序。在==符合数学意义上相等的场合,它还定义了严格全序。

4.2 可派生

  此trait可与#derive一起使用。

  在结构体上派生时,会按结构体成员从上到下的声明顺序生成字典序。

  在枚举上派生时,变体首先按其判别值排序,其次再按其字段排序。默认情况下,排在最上面的变体判别值最小,最下面的判别值最大。

rust 复制代码
#[derive(Eq, Ord, PartialEq, PartialOrd)]
enum E {
	Top,
	Bottom,
}

fn main() {
	println!("{}", E::Top < E::Bottom);
}

  但是,手动设置判别值可以覆盖这一默认行为:

rust 复制代码
#[derive(Eq, PartialEq, PartialOrd, Ord)]
enum E {
	Top = 2,
	Bottom = 1,
}

fn main() {
	println!("{}", E::Bottom < E::Top);
}

4.3 字典序比较

  字典序比较lexicographical comparison是具有以下性质的操作。

  两个序列逐元素比较。

  第一个不相等的元素决定哪个序列在字典序上更小或更大。

  如果一个序列是另一个序列的前缀,则较短的序列在字典序上更小。

  如果两个序列的元素------相等且长度相同,则两个字典序相等。

  空序列在字典序上小于任何非空序列。

  两个空序列字典序相等。

4.4 如何实现Ord

  Ord要求类型同时实现PartialOrd、PartialEq和Eq。

  由于Ord能推出比PartialOrd更强的排序关系,且Ord与PartialOrd必须保持一致,你必须先决定如何实现Ord。你可以选择派生它,也可以手动实现它。如果派生,就应当把四个trait全部派生;如果手动实现,就应当以Ord的实现为基础,手动实现全部四个trait。

rust 复制代码
use std::cmp::Ordering;

struct Character {
	health: u32,
	experience: u32,
	mana: f32,
}

impl Ord for Character {
	fn cmp(&self, other: &Self) -> Ordering {
		self.experience
			.cmp(&other.experience)
			.then(self.health.cmp(&other.health))
	}
}

impl PartialOrd for Character {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

impl PartialEq for Character {
	fn eq(&self, other: &Self) -> bool {
		self.health == other.health && self.experience == other.experience
	}
}

impl Eq for Character {}

  如果你只是想按照某个字段的值对某个类型做slice::sort(切片排序),使用slice::sort_by_key可能更简单。

4.5 错误实现示例

rust 复制代码
use std::cmp::Ordering;

#[derive(Debug)]
struct Character {
	health: f32,
}

impl Ord for Character {
	fn cmp(&self, other: &Self) -> Ordering {
		if self.health < other.health {
			Ordering::Less
		} else if self.health > other.health {
			Ordering::Greater
		} else {
			Ordering::Equal
		}
	}
}

impl PartialOrd for Character {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

impl PartialEq for Character {
	fn eq(&self, other: &Self) -> bool {
		self.health == other.health
	}
}

impl Eq for Character {}

fn main() {
	let a = Character { health: 4.5 };
	let b = Character { health: f32::NAN };
	// 错误:浮点值不构成全序,无视这一现实,直接用内建比较运算符去实现Ord
	// 并不能改变它。如果需要浮点值的全序,请改用f32::total_cmp
	
	// Ord的自反性要求不成立
	println!("{}", a == a);
	println!("{}", b != b);

	// Ord的反对称性要求不成立,a < c与c < a只允许一个为真
	// 不能两个都真,也不能两个都假
	println!("{}", (a < b) as u8 + (b < a) as u8);
}
rust 复制代码
use std::cmp::Ordering;

#[derive(Debug)]
struct Character {
	health: u32,
	experience: u32,
}

impl PartialOrd for Character {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

impl Ord for Character {
	fn cmp(&self, other: &Self) -> Ordering {
		if self.health < 50 {
			self.health.cmp(&other.health)
		} else {
			self.experience.cmp(&other.experience)
		}
	}
}

// 出于性能考虑,这样实现PartialEq并不地道,但在这个示例中
// 它保证了PartialEq、PartialOrd与Ord行为一致
impl PartialEq for Character {
	fn eq(&self, other: &Self) -> bool {
		self.cmp(other) == Ordering::Equal
	}
}

impl Eq for Character {}

fn main() {
	let a = Character {
		health: 3,
		experience: 5,
	};
	let b = Character {
		health: 10,
		experience: 77,
	};
	let c = Character {
		health: 143,
		experience: 2,
	};

	// 错误:Ord的实现会依据self.health的值去比较不同的字段,
	// 得到的顺序不是全序
	
	// Ord的传递要求不成立。若a小于b且b小于c,由传递性
	// a也必须小于c
	println!("{}", a < b && b < c && c < a);
	// Ord的反对称性要求也不成立。a < c与c < a只允许一个为真,
	// 不能两个都真,也不能两个都假
	println!("{}", (a < c) as u8 + (c < a) as u8);
}

4.6 必需方法

  方法签名:

rust 复制代码
fn cmp(&self, other: &Self) -> Ordering

  返回self与other之间的Ordering。如果表达式self <operator> other为真,则self.cmp(&other)返回与之匹配的顺序关系。

rust 复制代码
fn main() {
	println!("{:?}", 5.cmp(&10));
	println!("{:?}", 10.cmp(&5));
	println!("{:?}", 5.cmp(&5));
}

4.7 提供方法

4.7.1 max

  方法签名:

rust 复制代码
fn max(self, other: Self) -> Self
where
	Self: Sized,

  要求实现者在编译期是已知大小的。方法消耗自身和输入参数。比较并返回两个值中较大的一个。若比较判定两者相等,则返回第二个参数。

rust 复制代码
fn main() {
	println!("{}", 1.max(2));
	println!("{}", 2.max(2));
}
rust 复制代码
use std::cmp::Ordering;

#[derive(Eq)]
struct Equal(&'static str);

impl PartialEq for Equal {
	fn eq(&self, _other: &Self) -> bool {
		true
	}
}

impl PartialOrd for Equal {
	fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
		Some(Ordering::Equal)
	}
}

impl Ord for Equal {
	fn cmp(&self, _other: &Self) -> Ordering {
		Ordering::Equal
	}
}

fn main() {
	println!("{}", Equal("self").max(Equal("other")).0);
}

4.7.2 min

  方法签名:

rust 复制代码
fn min(self, other: Self) -> Self
where
	Self: Sized,

  要求实现者在编译期是已知大小的。方法消耗自身和输入参数。比较并返回两个值中较小的一个。若比较判定两者相等,则返回第一个参数。

rust 复制代码
fn main() {
	println!("{}", 1.min(2));
	println!("{}", 2.min(2));
}
rust 复制代码
use std::cmp::Ordering;

#[derive(Eq)]
struct Equal(&'static str);

impl PartialEq for Equal {
	fn eq(&self, _other: &Self) -> bool {
		true
	}
}

impl PartialOrd for Equal {
	fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
		Some(Ordering::Equal)
	}
}

impl Ord for Equal {
	fn cmp(&self, _other: &Self) -> Ordering {
		Ordering::Equal
	}
}

fn main() {
	println!("{}", Equal("self").min(Equal("other")).0);
}

4.7.3 clamp

  方法签名:

rust 复制代码
fn clamp(self, min: Self, max: Self) -> Self
where
	Self: Sized,

  要求实现者在编译期是已知大小的。方法消耗自身和输入参数。将一个值限制在某个区间内。若self大于max则返回max,若self小于min则返回min,否则返回self。min > max会panic。

rust 复制代码
fn main() {
	println!("{}", (-3).clamp(-2, 1));
	println!("{}", 0.clamp(-2, 1));
	println!("{}", 2.clamp(-2, 1));
}

5. Ordering枚举

rust 复制代码
#[repr(i8)]
pub enum Ordering {
	Less = -1,
	Equal = 0,
	Greater = 1,
}

  Ordering顺序关系是两个值之间比较的结果。

5.1 变体

  Less = -1

  表示被比较的值小于另一个值的顺序关系。

  Equal = 0

  表示被比较的值等于另一个值的顺序关系。

  Greater = 1

  表示被比较的值大于另一个值的顺序关系。

5.2 方法

5.2.1 is_eq

  方法签名:

rust 复制代码
pub const fn is_eq(self) -> bool

  方法消耗自身。当顺序关系是Equal变体时返回true。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less.is_eq());
	println!("{}", Ordering::Equal.is_eq());
	println!("{}", Ordering::Greater.is_eq());
}

5.2.2 is_ne

  方法签名:

rust 复制代码
pub const fn is_ne(self) -> bool

  方法消耗自身。当顺序关系不是Equal变体时返回true。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less.is_ne());
	println!("{}", Ordering::Equal.is_ne());
	println!("{}", Ordering::Greater.is_ne());
}

5.2.3 is_lt

  方法签名:

rust 复制代码
pub const fn is_lt(self) -> bool

  方法消耗自身。当顺序关系是Less变体时返回true。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less.is_lt());
	println!("{}", Ordering::Equal.is_lt());
	println!("{}", Ordering::Greater.is_lt());
}

5.2.4 is_gt

  方法签名:

rust 复制代码
pub const fn is_gt(self) -> bool

  方法消耗自身。当顺序关系是Greater变体时返回true。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less.is_gt());
	println!("{}", Ordering::Equal.is_gt());
	println!("{}", Ordering::Greater.is_gt());
}

5.2.5 is_le

  方法签名:

rust 复制代码
pub const fn is_le(self) -> bool

  方法消耗自身。当顺序关系是Less或Equal变体时返回true。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less.is_le());
	println!("{}", Ordering::Equal.is_le());
	println!("{}", Ordering::Greater.is_le());
}

5.2.6 is_ge

  方法签名:

rust 复制代码
pub const fn is_ge(self) -> bool

  方法消耗自身。当顺序关系是Greater或Equal变体时返回true。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less.is_ge());
	println!("{}", Ordering::Equal.is_ge());
	println!("{}", Ordering::Greater.is_ge());
}

5.2.7 reverse

  方法签名:

rust 复制代码
pub const fn reverse(self) -> Ordering

  方法消耗自身反转顺序关系。Less变为Greater,Greater变为Less,Equal保持Equal。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{:?}", Ordering::Less.reverse());
	println!("{:?}", Ordering::Equal.reverse());
	println!("{:?}", Ordering::Greater.reverse());
}

  此方法可用于反转比较:

rust 复制代码
fn main() {
	let data: &mut [_] = &mut [2, 10, 5, 8];
	// 将数组从大到小排序
	data.sort_by(|a, b| a.cmp(b).reverse());
	let b: &mut [_] = &mut [10, 8, 5, 2];
	println!("{}", data == b);
}

5.2.8 then

  方法签名:

rust 复制代码
pub const fn then(self, other: Ordering) -> Ordering

  方法消耗自身。链接两个顺序关系。当self不是Equal时返回self,否则返回other。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	let result = Ordering::Equal.then(Ordering::Less);
	println!("{:?}", result);
	let result = Ordering::Less.then(Ordering::Greater);
	println!("{:?}", result);
	let result = Ordering::Equal.then(Ordering::Equal);
	println!("{:?}", result);
	let x: (u32, u32, u32) = (1, 2, 7);
	let y: (u32, u32, u32) = (1, 5, 3);
	let result = x.0.cmp(&y.0).then(x.1.cmp(&y.1)).then(x.2.cmp(&y.2));
	println!("{:?}", result);
}

5.2.9 then_with

  方法签名:

rust 复制代码
pub fn then_with<F>(self, f: F) -> Ordering
where
	F: FnOnce() -> Ordering,

  参数f接受任何实现了FnOnce() -> Ordering的类型,包括闭包、函数指针和函数项。方法会消耗自身。用给定的f链接顺序关系。当self不是Equal时返回self,否则调用f并返回结果。

rust 复制代码
use std::cmp::Ordering;

fn main() {
	let result = Ordering::Equal.then_with(|| Ordering::Less);
	println!("{:?}", result);
	let result = Ordering::Less.then_with(|| Ordering::Equal);
	println!("{:?}", result);
	let result = Ordering::Greater.then_with(|| Ordering::Less);
	println!("{:?}", result);
	let result = Ordering::Equal.then_with(|| Ordering::Equal);
	println!("{:?}", result);
	let x: (u32, u32, u32) = (1, 2, 7);
	let y: (u32, u32, u32) = (1, 5, 3);
	let result = x.0.cmp(&y.0).then_with(|| x.1.cmp(&y.1)).then_with(|| x.2.cmp(&y.2));
	println!("{:?}", result);
}

5.3 Trait实现

5.3.1 Clone

rust 复制代码
use std::cmp::Ordering;

fn main() {
	let order = Ordering::Equal;
	let cloned = order.clone();
	println!("{:?}", cloned);
}

5.3.2 Debug(上面的例子给出了Debug打印)

5.3.3 Hash

rust 复制代码
use std::cmp::Ordering;
use std::hash::{BuildHasher, Hash, Hasher, RandomState};

fn main() {
    let order = Ordering::Equal;
    let state = RandomState::new();
    let mut hasher = state.build_hasher();
    order.hash(&mut hasher);
    println!("{:x}", hasher.finish());
}

5.3.4 Ord

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less > Ordering::Greater);
	println!("{}", Ordering::Less < Ordering::Equal);
	println!("{}", Ordering::Greater < Ordering::Less);
}

5.3.5 PartialEq

rust 复制代码
use std::cmp::Ordering;

fn main() {
	println!("{}", Ordering::Less == Ordering::Equal);
	println!("{}", Ordering::Equal != Ordering::Greater);
}

5.3.6 PartialOrd

5.3.7 Copy

5.3.8 Eq

6. Reverse

rust 复制代码
#[repr(transparent)]
pub struct Reverse<T>(pub T);

  一个用于反转排序的辅助结构体。

  该结构体是与Vec::sort_by_key之类的函数配合使用的辅助工具,可用于反转键中某一部分的顺序。

6.1 示例

rust 复制代码
use std::cmp::Reverse;

fn main() {
	let mut v = vec![1, 2, 3, 4, 5, 6];
	v.sort_by_key(|&num| (num > 3, Reverse(num)));
	println!("{:?}", v);
}

6.2 元组字段

0: T

6.3 Trait实现

6.3.1 Clone

  要求T也实现Clone。

rust 复制代码
use std::cmp::Reverse;

fn main() {
	let r = Reverse(1);
	let cloned = r.clone();
	println!("{:?}", cloned);
}

6.3.2 Debug(上面的例子给出了Debug打印)

  要求T也实现Debug。

6.3.3 Default

  要求T也实现Default。

rust 复制代码
use std::cmp::Reverse;

fn main() {
	let r = Reverse::<u32>::default();
	println!("{:?}", r);
}

6.3.4 Hash

  要求T也实现Hash。

rust 复制代码
use std::cmp::Reverse;
use std::hash::{BuildHasher, Hash, Hasher, RandomState};

fn main() {
	let state = RandomState::new();
	let mut hasher = state.build_hasher();
	Reverse(1).hash(&mut hasher);
	println!("{:x}", hasher.finish());
}

6.3.5 Ord

  要求T也实现Ord。

rust 复制代码
use std::cmp::Reverse;

fn main() {
	let r1 = Reverse(1);
	let r2 = Reverse(2);
	println!("{}", r1 > r2);
	let r1 = Reverse("abc");
	let r2 = Reverse("def");
	println!("{}", r1 < r2);
}

6.3.6 PartialEq

  要求T也实现PartialEq。

rust 复制代码
use std::cmp::Reverse;

fn main() {
	let r1 = Reverse(1);
	let r2 = Reverse(2);
	println!("{}", r1 == r2);
	println!("{}", r1 != r1);
}

6.3.7 PartialOrd

  要求T也实现PartialOrd。

6.3.8 Copy

  要求T也实现Copy。

6.3.9 Eq

  要求T也实现Eq。

7. 函数

7.1 max

  函数签名:

rust 复制代码
pub fn max<T>(v1: T, v2: T) -> T
where
	T: Ord,

  T必须实现Ord。比较并返回两个值中的最大值。如果比较判定两个值相等,则返回第二个值。内部使用了Ord::max的一个别名。

7.1.1 示例

rust 复制代码
use std::cmp;

fn main() {
	println!("{}", cmp::max(1, 2));
	println!("{}", cmp::max(2, 2));
}
rust 复制代码
use std::cmp::{self, Ordering};

#[derive(Eq)]
struct Equal(&'static str);

impl PartialEq for Equal {
	fn eq(&self, _other: &Self) -> bool { true }
}

impl PartialOrd for Equal {
	fn partial_cmp(&self, _other: &Self) -> Option<Ordering> { Some(Ordering::Equal) }
}
impl Ord for Equal {
	fn cmp(&self, _other: &Self) -> Ordering { Ordering::Equal }
}

fn main() {
	println!("{}", cmp::max(Equal("v1"), Equal("v2")).0);
}

7.2 max_by

  方法签名:

rust 复制代码
pub fn max_by<T, F>(v1: T, v2: T, compare: F) -> T
where
	F: FnOnce(&T, &T) -> Ordering,

  参数compare接受任何实现了FnOnce(&T, &T) -> Ordering的类型,包括闭包 、函数指针和函数项。返回两个值中相对于指定比较函数的最大值。如果比较判定两个值相等,则返回第二个参数。调用比较函数时保持参数顺序,即v1总是作为第一个参数传入,v2作为第二个参数传入。

7.2.1 示例

rust 复制代码
use std::cmp;

fn main() {
	let abs_cmp = |x: &i32, y: &i32| x.abs().cmp(&y.abs());
	let result = cmp::max_by(3, -2, abs_cmp);
	println!("{}", result);
	let result = cmp::max_by(1, -2, abs_cmp);
	println!("{}", result);
	let result = cmp::max_by(1, -1, abs_cmp);
	println!("{}", result);
}

7.3 max_by_key

  方法签名:

rust 复制代码
pub fn max_by_key<T, F, K>(v1: T, v2: T, f: F) -> T
where
	F: FnMut(&T) -> K,
	K: Ord,

  参数f接受任何实现了FnMut(&T) -> K的类型,包括闭包 、函数指针和函数项。K必须实现Ord。返回按指定函数求值后取值最大的那个元素。如果比较判定两个值相等,则返回第二个参数。

7.3.1 示例

rust 复制代码
use std::cmp;

fn main() {
	let result = cmp::max_by_key(3, -2, |x: &i32| x.abs());
	println!("{}", result);
	let result = cmp::max_by_key(1, -2, |x: &i32| x.abs());
	println!("{}", result);
	let result = cmp::max_by_key(1, -1, |x: &i32| x.abs());
	println!("{}", result);
}

7.4 min

  方法签名:

rust 复制代码
pub fn min<T>(v1: T, v2: T) -> T
where
	T: Ord,

  T必须实现Ord。比较并返回两个值中的最小值。如果比较判定两个值相等,则返回第一个值。内部使用了Ord::min的一个别名。

7.4.1 示例

rust 复制代码
use std::cmp;

fn main() {
	println!("{}", cmp::min(1, 2));
	println!("{}", cmp::min(2, 2));
}
rust 复制代码
use std::cmp::{self, Ordering};

#[derive(Eq)]
struct Equal(&'static str);

impl PartialEq for Equal {
	fn eq(&self, other: &Self) -> bool { true }
}

impl PartialOrd for Equal {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(Ordering::Equal) }
}

impl Ord for Equal {
	fn cmp(&self, other: &Self) -> Ordering { Ordering::Equal }
}

fn main() {
	println!("{}", cmp::min(Equal("v1"), Equal("v2")).0);
}

7.5 min_by

  方法签名:

rust 复制代码
pub fn min_by<T, F>(v1: T, v2: T, compare: F) -> T
where
	F: FnOnce(&T, &T) -> Ordering,

  参数compare接受任何实现了FnOnce(&T, &T) -> Ordering的类型,包括闭包 、函数指针和函数项。返回两个值中相对于指定比较函数的最小值。如果比较判定两个值相等,则返回第一个参数。调用比较函数时保持参数顺序,即v1总是作为第一个参数插入,v2作为第二个参数传入。

7.5.1 示例

rust 复制代码
use std::cmp;

fn main() {
	let abs_cmp = |x: &i32, y: &i32| x.abs().cmp(&y.abs());
	let result = cmp::min_by(2, -1, abs_cmp);
	println!("{}", result);
	let result = cmp::min_by(2, -3, abs_cmp);
	println!("{}", result);
	let result = cmp::min_by(1, -1, abs_cmp);
	println!("{}", result);
}

7.6 min_by_key

  方法签名:

rust 复制代码
pub fn min_by_key<T, F, K>(v1: T, v2: T, f: F) -> T
where
	F: FnMut(&T) -> K,
	K: Ord,

  参数f接受任何实现了FnMut(&T) -> K的类型,包括闭包 、函数指针和函数项。K必须实现Ord。返回安指定函数求值后取值最小的那个元素。如果比较判定两个值相等,则返回第一个参数。

7.6.1 示例

rust 复制代码
use std::cmp;

fn main() {
	let result = cmp::min_by_key(2, -1, |x: &i32| x.abs());
	println!("{}", result);
	let result = cmp::min_by_key(2, -3, |x: &i32| x.abs());
	println!("{}", result);
	let result = cmp::min_by_key(1, -1, |x: &i32| x.abs());
	println!("{}", result);
}

参考

1、Rust std::cmp

相关推荐
PC2005-cloud5 小时前
Rust学习笔记:错误处理——panic!、Result、Option与_运算符
rust
Amos_Web8 小时前
Rspack 源码解析(十):Hash 与 Asset 生成
前端·rust·源码阅读
柯南46688 小时前
【AI开发之Rust】第 12 课:并发模型与同步原语
rust·编程语言
音符犹如代码1 天前
RustFS 1.0.0 深度解析:GA 之后,它值得替代 MinIO 吗?
rust
qq_452396231 天前
第十三篇:《unsafe Rust 与 FFI:与 C 和系统调用交互》
rust
传奇开心果编程1 天前
【Rust入门练中学】 第1课:从零开始
开发语言·学习·rust
对象存储与RustFS1 天前
跨实现迁移 MinIO→RustFS:mc diff 静默返回才是最容易翻车的一步
后端·rust·开源