序文
この記事では、Rust の 4 つの主要な比較特性である PartialEq と Eq、PartialOrd と Ord のオブジェクトに関する詳細について説明し、理論とコード実装を取り上げます。
PartialEq と Eq、および PartialOrd と Ord を正式に紹介する前に、この記事ではまず、それらが従う数学理論、つまり等価関係を紹介します。
記事は主に 3 部に分かれており、第 1 部は数学における等式関係について説明し、第 2 部は第 2 ~ 5 部で主に PartialEq と Eq について説明し、第 3 部は第 6 部で主に議論します。 PartialOrd と Ord。コンテンツの説明には順序がある場合がありますが、章順に読むことをお勧めします。
声明
この記事の内容は、筆者の個人的な学習結果をまとめてまとめたものであり、個人のレベルによる表現の誤りがある可能性がありますので、読者の皆様のご加筆をお待ちしております。
- 著者:リーグ
- 開始アドレス: https://github.com/chaseSpace/rust_practices/blob/main/blogs/about_eq_ord_trait.md
- CSDN:https://blog.csdn.net/sc_lilei/article/details/129322616
- 発売時期:2023年3月3日
- ライセンス: CC-BY-NC-SA 4.0 (転載する場合は著者と出典を明記してください)
1. 数学における等価性
中学数学では、等関係(等価関係とも言います)とは何かを紹介します 等関係とは、2つの物体の等しいことを表す基本的な二項関係です。次の 3 つのプロパティを満たす必要があります。
- 再帰性 (再帰性): self は self と等しくなければなりません。つまり、
a=a
; - 対称性: ある場合は
a=b
、b=a
; a=b
推移性:和がある場合はb=c
、和も存在しますa=c
。
つまり、これら 3 つの性質を満たすことを、(完全な)等価関係を満たすといいます。これは簡単に理解できるので、あまり説明しません。
1.1 部分等価関係
単純な整数型や文字列型の場合は、あらゆる方向 (2 次元を含む、最初の要素は型空間内の任意の値、2 番目の要素はそれぞれの値) で比較できるため、完全な等価関係があると言えます。値は任意のメンバー プロパティ)ですが、ディメンションの 1 つを常に満たさない一部の型ではそうではありません
。一緒に見てみましょう:
文字列を例にとると、包括的な比較は各バイト値と文字列全体の長さです。
0.浮動小数点型
浮動小数点型には NaN (Not-a-number) という特別な値があり、これはどの値 (それ自体も含む) とも等しくなく、再帰性に直接違反します。このとき、主に非 NaN 浮動小数点数を比較するために、浮動小数点数の部分等価関係を定義する必要があります。
NaN は、IEEE 754-2008 標準のセクション 5.2「特別な値」で定義されています。NaN に加えて、他の 2 つの特別な値は正の無限大 (+無限大) と負の無限大 (-無限大) ですが、これら 2 つの値は自己反抗を満足させる。
浮動小数点型に加えて、数学にはコレクション型や関数型など、通常の合同関係を持たない型もあります。
1. コレクションの種類
集合 A={1,2,3}、B={1,3,2} があるとします。このとき、A と B は等しいか等しくありませんか? これはさまざまな角度から見る必要があります。コレクションに同じ要素が含まれているかどうかだけに注目する場合、それらは等しいと言えます。要素の順序の一貫性も厳密に要求する場合、それらは等しくありません。 。
実際のアプリケーションでは、「セットの等価性」と呼ばれる、セット内の特別な等価関係を定義 (実装) します。この特別な関係 (実装ロジック) では、2 つのセットの要素が同じであることのみが要求され、その他の要素は必要ありません。
2. 機能の種類
まず、浮動小数点数の NaN の観点から関数を見てみましょう。関数 A=f(x) と B=f(y) があるとします。x=y の場合、明らかに A の値も B に等しいですが、パラメータがある場合、z は何もありません。 つまり、f(z) に結果がないか、結果が不正であることを意味します。このとき、f(z) はそれ自身と等しいと言えますか?
それは明らかにうまくいきません。この例は、浮動小数点の例と同じ意味を持ちます。
次に、コレクション型の観点から関数をもう一度見てみましょう。関数 A=f(x) と B=g(x) があるとします。これらは 2 つの異なる関数であることに注意してください。この 2 つに同じ入力 x が与えられると、同じ結果になります このとき、f(x)とg(x)は等しいでしょうか?
集合と同様に、実際のアプリケーションでは、関数の等価性と呼ばれる関数内の特別な等価関係も定義 (Impl) します。この特別な関係(実装ロジック)では、2つの関数の実行結果の値が同じであることだけが要求され、関数の実行プロセスが同じであることは要求されません。
1.2 部分的平等と全体的平等の関係
部分的等価は完全な等価のサブセットです。つまり、2 つの要素が等価関係にある場合、それらの間にも部分的等価関係が存在する必要があります。プログラミング言語でのこれの実装も同じルールに従います。
1.3 概要
数学では、(完全な) 等価関係 (等価関係) の 3 つの主要な性質、つまり再帰性、対称性、推移性が定義されていますが、一部のデータ型の値または属性は 3 つの主要な性質に違反しているため、それらを満足するとは言えません。完全な等価関係ですが、現時点では、このタイプでは部分的な等価関係のみが実現されます。
部分等価関係では、この時点では特別な値を除外しているため、比較に使用される値も 3 つの特性を満たします。また、部分的平等は完全な平等関係のサブセットです。
2. プログラミングと数学の関係
数学はデータ、空間、変化を研究する巨大な学問です。問題を記述し、対処するための厳密な方法を提供するのに対し、プログラミングは問題の解決策をコンピュータ プログラムに変換するプロセスです。数学は重要な学問であると言えます。問題 プログラミングの理論的形式は問題のコード形式であり、問題を解決するための基礎は数学に由来します。
したがって、プログラミング言語の設計には多数の数学的概念やモデルが使用されますが、この記事で焦点を当てる等値関係はその一例です。
部分同値関係の概念はRust ライブラリのPartialEq
コメントドキュメントで言及されており、浮動小数点数の特殊な値 NaN も例として使用されています。
Eq
.commentation の注釈ドキュメントでは同値関係について言及しており、Eq
トレイトを満たす型については同値関係の 3 つのプロパティを満たす必要があることが明確に述べられています。
3. パーシャルEq
3.1 特性の定義
Rust における PartialEq のネーミングはその意味を明確に表していますが、数学における等価関係を忘れると、混乱することになります。まずその定義を見てみましょう。
pub trait PartialEq<Rhs: ?Sized = Self> {
fn eq(&self, other: &Rhs) -> bool;
fn ne(&self, other: &Rhs) -> bool {
!self.eq(other)
}
}
この定義では、次の 3 つの基本情報を取得できます。
- このトレイトには eq と ne の 2 つのメソッドが含まれており、ne にはデフォルトの実装があります。これを使用する場合、開発者は eq メソッドを実装するだけで済みます (ライブラリのドキュメントには、他に良い理由がない場合は ne メソッドを実装すべきではないことも明記されています)手動で実装) ;
- PartialEq バインディングの Rhs パラメーターの型は です
?Size
。つまり、動的サイズ型 (DST) と固定サイズ型 (ST) 型が含まれます (Rhs は、比較のためにメイン型によって使用される型です)。 - Rhs パラメータはデフォルトの型 (メインの型と一致する) を提供しますが、他の型
Self
にすることもできます。つまり、実際には、対応するものが実装されている限り、struct と比較することもできます。i32
PartialEq
Rust の lhs と rhs は、「左側」(左側) パラメーターと「右側」(右側) パラメーターを指します。
3.2 対応する演算子
これは比較的単純で、PartialEq は Eq と一致しており、 2 つの演算子==
に対応する eq メソッドと ne メソッドがあります!=
。整数、浮動小数点、文字列などの Rust の基本型のほとんどは PartialEq を実装しているため、等価比較に==
と を使用できます。!=
3.3 派生可能
英語の説明は 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 コードを出力します。
3.4 PartialEq を手動で実装する
上記のコードは例として、BookFormat
他のクレート配下のコードを追加できない(変更できない)ことを想定しています。この場合、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,所以可以比较相等性
}
3.5 異なるタイプの比較
上記の特性定義によれば、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
}
}
3.6 Rustの基本型がPartialEqを実装する方法
上で述べたように、Rust の基本的なタイプはすべて PartialEq を実装していますが、どのように実装されているのでしょうか? タイプごとに一連の impl コードを記述しますか? コードはどこにありますか?
IDE を使用している場合は、(IDE によって異なります) 任意の場所で Ctrl キーを押したままコードをクリックすると、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 トレイトを迅速に実装します。マクロをまだ知らない方のために説明すると、これはパターン コードを繰り返すためのルールを記述するためのプログラミング機能であり、繰り返しコードの量を大幅に減らすことができます。
4. 等式
PartialEq を理解すると、Eq は非常に理解しやすくなり、このセクションの主な内容は基本的に PartialEq と同じであるため、比較的簡潔になります。
4.1 特性の定義
次のように:
pub trait Eq: PartialEq<Self> {
fn assert_receiver_is_total_eq(&self) {
}
}
コードによれば、次の 2 つの重要な情報を取得できます。
- Eq は PartialEq から継承されます。
- Eq には PartialEq よりも 1 つだけメソッドがあり
assert_receiver_is_total_eq()
、デフォルトの実装があります。
まず、Eq は PartialEq を継承しているため、Eq を実装したい場合は、まず PartialEq を実装する必要があることを意味します。2 つ目はこのassert_receiver_is_total_eq()
メソッドです。簡単に言えば、型の各属性が Eq 機能を実装していることをアサートするために、派生構文によって内部的に使用されます。私たちユーザーにとっては、あまり注意を払う必要はありません。
4.2 対応する演算子
PartialEq と若干の違いはありません。
4.3 派生可能
PartialEq の使用と同様に、派生時には、継承関係により Eq と PartialEq が同時に存在する必要があることに注意してください。
#[derive(PartialEq, Eq)] // 顺序无关
struct Book {
name: String,
}
4.4 Eq の手動実装
コードを直接見てください。
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 は両方とも==
AND!=
演算子を使用しており、認識に違いはないということです。
4.5 異なるタイプの比較
PartialEq と若干の違いはありません。
4.6 Rustの基本型がEqを実装する方法
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 }
}
5. 浮動小数点数のテスト
現在、標準ライブラリには、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); // 相等!
}
6. 部分的な順序と順序
6.1 PartialEq および Eq との関係
PartialEq と Eq について話すとき、PartialOrd と Ord は常にトピックから切り離せないことがよくあります。なぜなら、これらは両方ともバイナリ比較関係であり、最初の 2 つは等価比較であり、後の 2 つは順序性 (サイズとも呼ぶことができます) であるためです。比較。最初の 2 つで使用される演算子は==
と!=
で、後の 2 つで使用される演算子は>
、 、=
、<
はい、 PartialOrd と Ord の比較結果には等しい が含まれており、この順序関係に基づいてデータを並べ替えることができます (sort )。
重要: 順序には平等が含まれます。
PartialEq の存在理由と同様に、PartialOrd の存在理由も、浮動小数点数、 Bool、Option、関数、クロージャ、他のタイプ。
PartialEq と Eq、PartialOrd と Ord は一緒になって、等価性や順序など、Rust のあらゆる種類のバイナリ比較関係を記述します。cmp.rs
したがって、上記では、PartialOrd と Ord の定義もファイル内にあることがわかります。
PartialOrd と Ord は文字通り、部分順序と全順序の関係に翻訳できます。これが実際に意味するものだからです。部分順序と全順序の概念は離散数学に由来しており、以下で詳しく説明します。
6.2 基本特性
PartialOrd と Ord は特定の基本プロパティも満たしており、PartialOrd は次の条件を満たします。
- 推移性: はいの場合
a<b
、b<c
、その後a<c
。と同じです>
;==
- コントラスト: 「はい」の場合は
a<b
、b>a
「;」
Ord は PartialOrd に基づいており、自然に推移性と対立性に従います。さらに、任意の 2 つの要素について、次の特性も満たします。
- 確実性:関係の 1 つが
>
存在する必要があります。==
<
6.3 特性の定義
1.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))
}
}
基本情報:
- PartialOrd は PartialEq を継承しており、これはよく理解されていますが、サイズを比較できない型は同等かどうかを比較してはなりません。
partial_cmp()
メインの型と他の型のパラメーターを比較するためのメソッドを提供し、それを返しますOption<Ordering>
。これは、2 つの間の関係が比較できない (なし) 可能性があることを示します。したがって、ここでは、
Ord 特性の戻り値は次のとおりであると考えることができますOrdering
(なぜなら、それは合計の順序を持っています。タイプは比較できないものではありません)。- 他の 4 つのメソッドは、対応する演算子、 、 、 を実装します。
<
つまり<=
、>
PartialOrd>=
を実装する型は、これらの演算子を比較に使用できます。また、これらは PartialEq を継承しているため、 を使用することもできます==
。!=
PartialOrd と Ord の両方に等価関係が含まれていることをもう一度思い出してください。
2. オードの特性
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 とparameters の間のバイナリ関係other
と戻り値のOrdering
型 ( PartialOrd.partial_cmp() によって返されるものとは異なりますOption<Ordering>
) を比較するために使用されます。- Ord は Eq+PartialOrd を継承しており、これもよく理解されています。全順序関係を持つ型には、当然半順序関係もあります。
- 自分自身と引数の間で小さい/大きい値
min/max()
を返すメソッドを提供します。other
clamp()
入力パラメータ範囲内の値を返す追加のメソッドが提供されています。- 明らかに、PartialOrd を継承しているため、Ord を実装する型は演算子
<
,<=
,>
,>=
,==
,!=
;を使用できます。
正しい
Self: ~ const Destruct
説明: の後には型制約があり、制約の型は特性Self
を実装するDestruct
定数へのネイキッド ポインタである必要があります。
全順序と部分順序の概念 (離散数学から)
- 全順序: つまり、全順序関係。これは当然二項関係です。合計順序とは、セット内の比較可能な 2 つの要素間の関係を指します。たとえば、実数内の任意の 2 つの数値の大きさを比較することができます。その場合、「サイズ」は実数セットの全体の順序関係になります。
- 部分順序: コレクション内の一部の要素のみを比較できる関係。たとえば、複素数セット内のすべての数値のサイズを比較できるわけではない場合、「サイズ」は複素数セットの半順序関係になります。
- 明らかに、合計注文は部分注文である必要があります。その逆は真実ではありません。
6.4 導出可能
1. PartialOrd の導出
PartialOrd と Ord はマクロを使用して自動的に実装することもできますderive
。コードは次のとおりです。
#[derive(PartialOrd, PartialEq)]
struct Book {
name: String,
}
#[derive(PartialOrd, PartialEq)]
enum BookFormat {
Paperback, Hardback, Ebook }
注意すべき点がいくつかあります。
- 継承関係により、PartialEq も同時に導出する必要があります。
- PartialEq と比較すると、
union
型の導出はサポートされていません。 - 構造体を導出する場合、サイズの順序はメンバー フィールドの辞書の順序 (アルファベット順、数値と文字の比較は ASCII テーブル エンコーディング、および数値エンコーディング < 文字エンコーディングに基づきます。複数を比較する場合) に基づきます。 -中国語などのバイト文字は、Unicode エンコード後に比較に切り替えます。
実際、ASCII テーブルの文字エンコードは、対応する Unicode エンコードと一致します)。 - 列挙型を取得するとき、サイズの順序は列挙型の値のサイズに基づきます。デフォルトでは、最初の列挙型の値は 1 で、1 ずつ下方に増加するため、最初の列挙型が最小になります。
以下では、コードを使用してポイント 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);
辞書編集上の比較ルールには、次のような特殊なケースがいくつかあります。
- 要素 A が要素 B のプレフィックスである場合、要素 A<要素 B になります。
- 空の文字列 < 空でない単語列。
2. オード導出
#[derive(Ord, Eq, PartialOrd, PartialEq)]
struct Book {
name: String,
}
#[derive(Ord, Eq, PartialOrd, PartialEq)]
enum BookFormat {
Paperback,
Hardback,
Ebook,
}
ここで注意すべき点が 1 つあります。継承関係により、Ord は Eq、PartialOrd、および PartialEq から同時に派生する必要があるということです。さらに、上記によると、PartialOrd と Ord の両方がサポートされているという>=
こと<=
を覚えておく必要があります。
6.5 PartialOrd と Ord を手動で実装する
1.PartialOrd実装
// 注意这里测试对象是Book3,不要为成员字段format即BookFormat3派生任何trait,模拟实际项目中无法修改成员字段特性的情况
enum BookFormat3 {
Paperback,
Hardback,
Ebook,
}
struct Book3 {
name: String,
format: BookFormat3,
}
// -- 先得实现 PartialEq
impl PartialEq<Self> for Book3 {
// tips:这里可以将<Self>省略
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)
}
}
2. オード・インプル
// 测试对象: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))
}
}
このコードを読むときに注意すべき点がいくつかあります。
- まずコードのコメントを読んでください。
- Ord が最初に実装され、次に PartialOrd が実装されることに注意してください。その理由は、最初から型に対して Ord を実装したいため、型が肯定的な結果 (None ではない) を取得できることを意味するためです。 PartialOrd を実装した後に Ord を呼び出します
cmp()
。
6.6 異なるタイプの比較
このセクションではコードは掲載せず、実装は読者に任せています。具体的な実装方法については、前節 3.5 または 4.5 の内容を参照できます。
6.7 Rustの基本型がPartialOrdとOrdを実装する方法
1. PartialOrd implマクロ
上記と同じ方法で PartialOrd の実装マクロを見つけますcmp.rs
。コードは次のとおりです。
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!
2 つの比較結果を通じて最終結果を取得します (コメントを参照)。 - このマクロは浮動小数点型だけでなく、
()
合計bool
型にも適用されます。言うまでもなく、浮動小数点数型、ユニット型がソートに使用される単一値型であることは比較的まれです。bool 型にこの特性を実装する理由は、bool を含む構造体または列挙型をソートする必要がある場合があるためです
。型メンバーであるため、PartialOrd を実装する必要があります (その実装は とも呼ばれることに注意してくださいself.cmp()
)。
ここでのconst キーワード
impl const
は、実行時に追加のオーバーヘッドが発生しないように、この特性実装をコンパイル時定数 (コンパイル時最適化) としてマークすることを意味します。これは、fn partial_cmp()
実装がデータを追加する前にデータを変更しないconst
ためです。もちろん、動的に割り当てられたメモリ (Box や Vec など) を使用できないこと、非 const 関数を呼び出すことができないことなど、他の要件もあります。 、など。
2. Ord impl マクロ
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 <https://github.com/rust-lang/rust/issues/63758> 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 <https://github.com/rust-lang/rust/issues/66780> 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 }
}
知っておくべきことがいくつかあります:
- Ord を実装する場合、PartialOrd も同時に実装する必要があり、実装の順序は必要ありません。PartialOrd の内部部分は
partial_cmp()
Ord を呼び出しますcmp()
。これは前述の理由です。 ()
Ord はbool 型にも実装されます。char usize u8 u16 ...
Ord はほとんどのプリミティブ型に実装されています。
6.8 他の型の 4 つの比較特性を実装する
ここで参照する他の型は!
、、、であり不可变借用类型
、可变借用类型
特定の実装コードは、ソース コードに示されているマクロのすぐ下にあるord_impl!
ため、ここでは詳細には触れません。