精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文講透Rust中的PartialEq和Eq

jf_wN0SrCdH ? 來(lái)源:Rust語(yǔ)言中文社區(qū) ? 2023-03-07 09:43 ? 次閱讀

前言

本文將圍繞對(duì)象:PartialEq和Eq,以及PartialOrd和Ord,即四個(gè)Rust中重點(diǎn)的Compare Trait進(jìn)行討論并解釋其中的細(xì)節(jié),內(nèi)容涵蓋理論以及代碼實(shí)現(xiàn)。

在正式介紹PartialEq和Eq、以及PartialOrd和Ord之前,本文會(huì)首先介紹它們所遵循的數(shù)學(xué)理論,也就是相等關(guān)系。文章主要分三大部分,第一部分是第1節(jié),討論的是數(shù)學(xué)中的相等關(guān)系;第二部分是第2~5節(jié),主要討論P(yáng)artialEq和Eq;第三部分是第6節(jié),主要討論P(yáng)artialOrd和Ord。內(nèi)容描述可能具有先后順序,建議按章節(jié)順序閱讀。

聲明

本文內(nèi)容來(lái)自作者個(gè)人的學(xué)習(xí)成果總結(jié)及整理,可能會(huì)存在因個(gè)人水平導(dǎo)致的表述錯(cuò)誤,歡迎并感謝讀者指正!

  • 作者:Leigg

  • 首發(fā)地址:https://github.com/chaseSpace/rust_practices/blob/main/blogs/about_eq_ord_trait.md

  • CSDN:https://blog.csdn.net/sc_lilei/article/details/129322616

  • 發(fā)布時(shí)間:2023年03月03日

  • License:CC-BY-NC-SA 4.0 (轉(zhuǎn)載請(qǐng)注明作者及來(lái)源)

1. 數(shù)學(xué)中的相等關(guān)系

在初中數(shù)學(xué)中,會(huì)介紹到什么是相等關(guān)系(也叫等價(jià)關(guān)系),相等關(guān)系是一種基本的二元關(guān)系,它描述了兩個(gè)對(duì)象之間的相等性質(zhì)。它必須滿足如下三個(gè)性質(zhì):

  • 自反性(反身性):自己一定等于自己,即a=a

  • 對(duì)稱(chēng)性:若有a=b,則有b=a

  • 傳遞性:若有a=bb=c,則有a=c

也就是說(shuō),滿足這三個(gè)性質(zhì)才叫滿足(完全)相等關(guān)系。這很容易理解,就不過(guò)多解釋。

1.1 部分相等關(guān)系

對(duì)于簡(jiǎn)單的整數(shù)類(lèi)型、字符串類(lèi)型,我們可以說(shuō)它們具有完全相等關(guān)系,因?yàn)樗鼈兛梢匀轿槐容^(包含兩個(gè)維度,第一個(gè)是類(lèi)型空間中的任意值,第二個(gè)是每個(gè)值的任意成員屬性), 但是對(duì)于某些類(lèi)型就不行了,這些類(lèi)型總是不滿足其中一個(gè)維度。下面一起來(lái)看看:

以字符串為例,全方位比較的是它的每個(gè)字節(jié)值以及整個(gè)字符串的長(zhǎng)度。

0. 浮點(diǎn)數(shù)類(lèi)型

在浮點(diǎn)數(shù)類(lèi)型中有個(gè)特殊的值是NaN(Not-a-number),這個(gè)值與任何值都不等(包括自己),它直接違背了自反性。這個(gè)時(shí)候,我們就需要為浮點(diǎn)數(shù)定義一種部分相等關(guān)系,這主要是為了比較非NaN浮點(diǎn)數(shù)。

NaN定義于IEEE 754-2008標(biāo)準(zhǔn)的5.2節(jié)“特殊值”(Special Values)中,除了NaN,另外兩個(gè)特殊值是正無(wú)窮大(+infinity)、負(fù)無(wú)窮大(-infinity),不過(guò)這兩個(gè)值滿足自反性。

除了浮點(diǎn)數(shù)類(lèi)型,數(shù)學(xué)中還有其他類(lèi)型也不具有通常意義上的全等關(guān)系,比如集合類(lèi)型、函數(shù)類(lèi)型。

1. 集合類(lèi)型

假設(shè)有集合A={1,2,3}、B={1,3,2},那么此時(shí)A和B是相等還是不相等呢?這就需要在不同角度去看待,當(dāng)我們只關(guān)注集合中是否包含相同的元素時(shí), 可以說(shuō)它們相等,當(dāng)我們還要嚴(yán)格要求元素順序一致時(shí),它們就不相等。

在實(shí)際應(yīng)用中,由我們定義(Impl)了一種集合中的特殊相等關(guān)系,稱(chēng)為"集合的相等",這個(gè)特殊關(guān)系(實(shí)現(xiàn)邏輯)中,我們只要求兩個(gè)集合的元素相同,不要求其他。

2. 函數(shù)類(lèi)型

首先從浮點(diǎn)數(shù)的NaN角度來(lái)看函數(shù),假設(shè)有函數(shù)A=f(x)、B=f(y),若x=y,那顯然A的值也等于B,但是如果存在一個(gè)參數(shù)z是無(wú)意義的呢,意思是f(z)是無(wú)結(jié)果的或結(jié)果非法,那么此時(shí)可以說(shuō)f(z)等于自身嗎?那顯然是不行的。這個(gè)例子和浮點(diǎn)數(shù)的例子是一個(gè)意思。

然后從集合類(lèi)型的角度再來(lái)看一次函數(shù),假設(shè)有函數(shù)A=f(x)、B=g(x),注意是兩個(gè)不同的函數(shù),當(dāng)二者給定一個(gè)相同輸入x產(chǎn)生相同結(jié)果時(shí),此時(shí)f(x)和g(x)是相等還是不等呢?與集合類(lèi)似,實(shí)際應(yīng)用中,這里也是由我們定義(Impl)了一種函數(shù)中的特殊相等關(guān)系,稱(chēng)為函數(shù)的相等。這個(gè)特殊關(guān)系(實(shí)現(xiàn)邏輯)中,我們只要求兩個(gè)函數(shù)執(zhí)行結(jié)果的值相同,不要求函數(shù)執(zhí)行過(guò)程相同。

1.2 部分相等與全相等的關(guān)系

部分相等是全相等關(guān)系的子集,也就是說(shuō),如果兩個(gè)元素具有相等關(guān)系,那它們之間也一定有部分相等關(guān)系。這在編程語(yǔ)言中的實(shí)現(xiàn)也是同樣遵循的規(guī)則。

1.3 小結(jié)

數(shù)學(xué)中定義了(全)相等關(guān)系(等價(jià)關(guān)系)的三大性質(zhì),分別是自反性、對(duì)稱(chēng)性和傳遞性;但某些數(shù)據(jù)類(lèi)型中的值或?qū)傩赃`背了三大性質(zhì),就不能叫做滿足全相等關(guān)系, 此時(shí)只能為該類(lèi)型實(shí)現(xiàn)部分相等關(guān)系。

在部分相等關(guān)系中,用于比較的值也是滿足三大性質(zhì)的,因?yàn)榇藭r(shí)我們排除了那些特殊值。另外,部分相等是全相等關(guān)系的子集。

2. 編程與數(shù)學(xué)的關(guān)系

數(shù)學(xué)是一門(mén)研究數(shù)據(jù)、空間和變化的龐大學(xué)科,它提供了一種嚴(yán)謹(jǐn)?shù)拿枋龊吞幚韱?wèn)題的方式,而編程則是將問(wèn)題的解決方法轉(zhuǎn)化為計(jì)算機(jī)程序的過(guò)程,可以說(shuō),數(shù)學(xué)是問(wèn)題的理論形式, 編程則是問(wèn)題的代碼形式,編程解決問(wèn)題的依據(jù)來(lái)自數(shù)學(xué)。

所以說(shuō),編程語(yǔ)言的設(shè)計(jì)中也是大量運(yùn)用了數(shù)學(xué)概念與模型的,本文關(guān)注的相等關(guān)系就是一個(gè)例子。

在Rust庫(kù)中的PartialEq的注釋文檔中提到了partial equivalence relations即部分相等關(guān)系這一概念,并且同樣使用了浮點(diǎn)數(shù)的特殊值NaN來(lái)舉例說(shuō)明。

Eq的注釋文檔則是提到了equivalence relations,并且明確說(shuō)明了,對(duì)于滿足Eqtrait的類(lèi)型,是一定滿足相等關(guān)系的三大性質(zhì)的。

3. PartialEq

3.1 trait定義

Rust中的PartialEq的命名明確地表達(dá)了它的含義,但如果我們忘記了數(shù)學(xué)中的相等關(guān)系,就肯定會(huì)對(duì)此感到疑惑。先來(lái)看看它的定義:

pub trait PartialEq<Rhs: ?Sized = Self> {
    fn eq(&self, other: &Rhs) -> bool;
    fn ne(&self, other: &Rhs) -> bool {
        !self.eq(other)
    }
}

在這個(gè)定義中,可以得到三個(gè)基本信息

  1. 這個(gè)trait包含2個(gè)方法,eq和ne,且ne具有默認(rèn)實(shí)現(xiàn),使用時(shí)開(kāi)發(fā)者只需要實(shí)現(xiàn)eq方法即可(庫(kù)文檔也特別說(shuō)明,若沒(méi)有更好的理由,則不應(yīng)該手動(dòng)實(shí)現(xiàn)ne方法);

  2. PartialEq綁定的Rhs參數(shù)類(lèi)型是?Size,即包括動(dòng)態(tài)大小類(lèi)型(DST)和固定大小類(lèi)型(ST)類(lèi)型(Rhs是主類(lèi)型用來(lái)比較的類(lèi)型);

  3. Rhs參數(shù)提供了默認(rèn)類(lèi)型即Self(和主類(lèi)型一致),但也可以是其他類(lèi)型,也就是說(shuō),實(shí)踐中你甚至可以將i32與struct進(jìn)行比較,只要實(shí)現(xiàn)了對(duì)應(yīng)的PartialEq

Rust中的lhs和rhs指的是,"left-hand side"(左手邊) 和 "right-hand side"(右手邊)的參數(shù)。

3.2 對(duì)應(yīng)操作符

這個(gè)比較簡(jiǎn)單,PartialEq和Eq一致,擁有的eq和ne方法分別對(duì)應(yīng)==!=兩個(gè)操作符。Rust的大部分基本類(lèi)型如整型、浮點(diǎn)數(shù)、字符串等都實(shí)現(xiàn)了PartialEq, 所以它們可以使用==!=進(jìn)行相等性比較。

3.3 可派生

英文描述為Derivable,即通過(guò)derive宏可以為自定義復(fù)合類(lèi)型(struct/enum/union類(lèi)型)自動(dòng)實(shí)現(xiàn)PartialEq,用法如下:

#[derive(PartialEq)]
struct Book {
    name: String,
}

#[derive(PartialEq)]
enum BookFormat { Paperback, Hardback, Ebook }

#[derive(PartialEq)]
union T {
    a: u32,
    b: f32,
    c: f64,
}

需要注意的是,可派生的前提是這個(gè)復(fù)合類(lèi)型下的所有成員字段都是支持PartialEq的,下面的代碼說(shuō)明了這種情況:

// #[derive(PartialEq)]  // 取消注釋即可編譯通過(guò)
enum BookFormat { Paperback, Hardback, Ebook }

// 無(wú)法編譯!!!
#[derive(PartialEq)]
struct Book {
    name: String,
    format: BookFormat, // 未實(shí)現(xiàn)PartialEq
}

擴(kuò)展:使用cargo expand命令可以打印出宏為類(lèi)型實(shí)現(xiàn)的PartialEq代碼。

3.4 手動(dòng)實(shí)現(xiàn)PartialEq

以上一段代碼為例,我們假設(shè)BookFormat是引用其他crate下的代碼,無(wú)法為其添加derive語(yǔ)句(不能修改它),此時(shí)就需要手動(dòng)為Book手動(dòng)實(shí)現(xiàn)PartialEq,代碼如下:

enum BookFormat { Paperback, Hardback, Ebook }

struct Book {
    name: String,
    format: BookFormat,
}

// 要求只要name相等則Book相等(假設(shè)format無(wú)法進(jìn)行相等比較)
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); // 因?yàn)锽ook實(shí)現(xiàn)了PartialEq,所以可以比較相等性
}

3.5 比較不同的類(lèi)型

根據(jù)上面的trait定義中,我們知道了只要在實(shí)現(xiàn)PartialEq時(shí)關(guān)聯(lián)不同類(lèi)型的Rhs參數(shù),就能比較不同類(lèi)型的相等性。示例代碼如下:

#[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);  // 無(wú)法反過(guò)來(lái)比較
}

需要注意的是,代碼片段中僅實(shí)現(xiàn)了Car與Wheel的相等性比較,若要反過(guò)來(lái)比較,還得提供反向的實(shí)現(xiàn),如下:

impl PartialEq<Car> for WheelBrand {
    fn eq(&self, other: &Car) -> bool {
        *self == other.brand
    }
}

3.6 Rust基本類(lèi)型如何實(shí)現(xiàn)PartialEq

上文說(shuō)過(guò),Rust的基本類(lèi)型都實(shí)現(xiàn)了PartialEq,那具體是怎么實(shí)現(xiàn)的呢?是為每個(gè)類(lèi)型都寫(xiě)一套impl代碼嗎?代碼在哪呢?

如果你使用IDE,可以通過(guò)在任意位置按住ctrl鍵(視IDE而定)點(diǎn)擊代碼中的PartialEq以打開(kāi)其在標(biāo)準(zhǔn)庫(kù)中的代碼文件cmp.rs,相對(duì)路徑是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強(qiáng)大的宏特性(此處使用的是聲明宏,還算簡(jiǎn)單),來(lái)為Rust的眾多基本類(lèi)型快速實(shí)現(xiàn)了PartialEq trait。如果你還不了解宏,可以暫且理解其是一種編寫(xiě)重復(fù)模式代碼規(guī)則的編程特性,它可以減少大量重復(fù)代碼。

4. Eq

理解了PartialEq,那Eq理解起來(lái)就非常簡(jiǎn)單了,本節(jié)的內(nèi)容主體與PartialEq基本一致,所以相對(duì)簡(jiǎn)明。

4.1 trait定義

如下:

pub trait Eq: PartialEq<Self> {
    fn assert_receiver_is_total_eq(&self) {}
}

根據(jù)代碼可以得到兩個(gè)重要信息:

  1. Eq是繼承自PartialEq的;

  2. Eq相對(duì)PartialEq只多了一個(gè)方法assert_receiver_is_total_eq(),并且有默認(rèn)實(shí)現(xiàn);

第一個(gè),既然Eq繼承自PartialEq,說(shuō)明想要實(shí)現(xiàn)Eq,必先實(shí)現(xiàn)PartialEq。第二個(gè)是這個(gè)assert_receiver_is_total_eq()方法了,簡(jiǎn)單來(lái)說(shuō),它是被derive語(yǔ)法內(nèi)部使用的,用來(lái)斷言類(lèi)型的每個(gè)屬性都實(shí)現(xiàn)了Eq特性,對(duì)于使用者的我們來(lái)說(shuō), 其實(shí)不用過(guò)多關(guān)注。

4.2 對(duì)應(yīng)操作符

與PartialEq無(wú)差別,略。

4.3 可派生

與PartialEq的使用相似,只是要注意派生時(shí),由于繼承關(guān)系,Eq和PartialEq必須同時(shí)存在。

#[derive(PartialEq, Eq)] // 順序無(wú)關(guān)
struct Book {
    name: String,
}

4.4 手動(dòng)實(shí)現(xiàn)Eq

直接看代碼:

enum BookFormat { Paperback, Hardback, Ebook }

struct Book {
    name: String,
    format: BookFormat,
}

// 要求只要name相等則Book相等(假設(shè)format無(wú)法進(jìn)行相等比較)
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);
}

需要注意的是,必須先實(shí)現(xiàn)PartialEq,再實(shí)現(xiàn)Eq。另外,這里能看出的是,在比較相等性方面,Eq和PartialEq都是使用==!=操作符,無(wú)差別感知。

4.5 比較不同的類(lèi)型

與PartialEq無(wú)差別,略。

4.6 Rust基本類(lèi)型如何實(shí)現(xiàn)Eq

與PartialEq一樣,在相對(duì)路徑為RUST_LIB_DIR/core/src/cmp.rs的文件中,存在如下宏代碼:

mod impls {
    /*
        ... (先實(shí)現(xiàn)PartialEq)
        
    */

    // 再實(shí)現(xiàn)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. 對(duì)浮點(diǎn)數(shù)的測(cè)試

目前在標(biāo)準(zhǔn)庫(kù)中,筆者只發(fā)現(xiàn)有浮點(diǎn)數(shù)是只實(shí)現(xiàn)了PartialEq的(以及包含浮點(diǎn)數(shù)的復(fù)合類(lèi)型),下面是浮點(diǎn)數(shù)的測(cè)試代碼:

fn main() {
    fn check_eq_impl<I: Eq>(typ: I) {}
    // check_eq_impl(0.1f32); // 編譯錯(cuò)誤
    // check_eq_impl(0.1f64); // 編譯錯(cuò)誤

    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. PartialOrd和Ord

6.1 與PartialEq和Eq的關(guān)系

很多時(shí)候,當(dāng)我們談到PartialEq和Eq時(shí),PartialOrd和Ord總是不能脫離的話題,因?yàn)樗鼈兌际且环N二元比較關(guān)系,前兩者是相等性比較,后兩者是有序性(也可稱(chēng)大小性)比較。前兩者使用的操作符是==!=,后兩者使用的操作符是>=<,沒(méi)錯(cuò),PartialOrd和Ord的比較結(jié)果是包含等于的,然后我們可以基于這個(gè)有序關(guān)系來(lái)對(duì)數(shù)據(jù)進(jìn)行排序(sort)。

重點(diǎn):有序性包含相等性。

與PartialEq存在的原因一樣,PartialOrd的存在的理由也是因?yàn)橛幸恍╊?lèi)型是不具有有序性關(guān)系的(無(wú)法比較),比如浮點(diǎn)數(shù)、Bool、Option、函數(shù)、閉包等類(lèi)型。

PartialEq和Eq、PartialOrd和Ord共同描述了Rust中任意類(lèi)型的二元比較關(guān)系,包含相等性、有序性。所以在上文中,你可能也觀察到PartialOrd和Ord的定義也位于cmp.rs文件中。

我們可以將PartialOrd和Ord直譯為偏序和全序關(guān)系,因?yàn)檫@確實(shí)是它們要表達(dá)的含義。偏序和全序的概念來(lái)自離散數(shù)學(xué),下文詳解。

6.2 基本性質(zhì)

PartialOrd和Ord也是滿足一定的基本性質(zhì)的,PartialOrd滿足:

  • 傳遞性:若有ab,則a。且>==也是一樣的;

  • 對(duì)立性:若有a,則b>a

Ord基于PartialOrd,自然遵循傳遞性和對(duì)立性,另外對(duì)于任意兩個(gè)元素,還滿足如下性質(zhì):

  • 確定性:必定存在>==<其中的一個(gè)關(guān)系;

6.3 trait定義

1. PartialOrd trait

// 二元關(guān)系定義(<,==,>)
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))
    }
}

基本信息:

  1. PartialOrd繼承自PartialEq,這很好理解,無(wú)法比較大小的類(lèi)型也一定不能進(jìn)行相等性比較;

  2. 提供partial_cmp()方法用于主類(lèi)型和可以是其他類(lèi)型的參數(shù)比較,返回的Option,表示兩者關(guān)系可以是無(wú)法比較的(None),那么這里我們就可以聯(lián)想到Ord trait返回的肯定是Ordering(因?yàn)榫哂腥虻念?lèi)型不會(huì)存在無(wú)法比較的情況);

  3. 另外四個(gè)方法分別實(shí)現(xiàn)了對(duì)應(yīng)的操作符:<,<=,>,>=,即實(shí)現(xiàn)了PartialOrd的類(lèi)型可以使用這些操作符進(jìn)行比較;除此之外,由于繼承了PartialEq,所以還允許使用==,!=

請(qǐng)?jiān)俅斡涀。还苁荘artialOrd還是Ord,都包含相等關(guān)系。

2. Ord trait

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
        }
    }
}

基本信息:

  1. cmp方法用于比較self與參數(shù)other的二元關(guān)系,返回Ordering類(lèi)型(區(qū)別于PartialOrd.partial_cmp()返回的Option);

  2. Ord繼承自Eq+PartialOrd,這也很好理解,具有全序關(guān)系的類(lèi)型自然具有偏序關(guān)系;

  3. 提供min/max()方法以返回self與參數(shù)other之間的較小值/較大值;

  4. 額外提供clamp()方法返回輸入的參數(shù)區(qū)間內(nèi)的值;

  5. 顯然,由于繼承了PartialOrd,所以實(shí)現(xiàn)了Ord的類(lèi)型可以使用操作符<,<=,>,>=,==,!=

對(duì)Self: ~ const Destruct的解釋?zhuān)何挥趙here后即是類(lèi)型約束,這里約束了Self類(lèi)型必須是實(shí)現(xiàn)了Destructtrait的一個(gè)指向常量的裸指針。

全序和偏序的概念(來(lái)自離散數(shù)學(xué))

  • 全序:即全序關(guān)系,自然也是一種二元關(guān)系。全序是指,集合中的任兩個(gè)元素之間都可以比較的關(guān)系。比如實(shí)數(shù)中的任兩個(gè)數(shù)都可以比較大小,那么“大小”就是實(shí)數(shù)集的一個(gè)全序關(guān)系。

  • 偏序:集合中只有部分元素之間可以比較的關(guān)系。比如復(fù)數(shù)集中并不是所有的數(shù)都可以比較大小,那么“大小”就是復(fù)數(shù)集的一個(gè)偏序關(guān)系。

  • 顯然,全序關(guān)系必是偏序關(guān)系。反之不成立。

6.4 可派生

1. PartialOrd derive

PartialOrd和Ord也是可以使用derive宏進(jìn)行自動(dòng)實(shí)現(xiàn)的,代碼如下:

#[derive(PartialOrd, PartialEq)]
struct Book {
    name: String,
}

#[derive(PartialOrd, PartialEq)]
enum BookFormat { Paperback, Hardback, Ebook }

這里有幾點(diǎn)需要注意:

  1. 由于繼承關(guān)系,所以必須同時(shí)派生PartialEq;

  2. 與PartialEq相比,不支持為union類(lèi)型派生;

  3. 對(duì)struct進(jìn)行派生時(shí),大小順序依據(jù)的是成員字段的字典序(字母表中的順序,數(shù)字與字母比較則根據(jù)ASCII表編碼,數(shù)字編碼<字母編碼;若比較多字節(jié)字符如中文,則轉(zhuǎn)Unicode編碼后再比較; 實(shí)際上ASCII表中的字符編碼與對(duì)應(yīng)Unicode編碼一致);

  4. 對(duì)enum進(jìn)行派生時(shí),大小順序依據(jù)的是枚舉類(lèi)型的值大小,默認(rèn)情況下,第一個(gè)枚舉類(lèi)型的值是1,向下遞增1,所以第一個(gè)枚舉最小;

下面使用代碼對(duì)第2,3點(diǎn)舉例說(shuō)明:

#[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() });
// 字典序中,數(shù)字<字母(按ASCII編碼排序)
assert!(Book { name: "1".to_string() } < Book { name: "2".to_string() });
assert!(Book { name: "2".to_string() } < Book { name: "a".to_string() });
// 字典序中,如果比較多字節(jié)字符,則先轉(zhuǎn)為其Unicode的十六進(jìn)制形式,然后逐字節(jié)比較
// 比如 中文 "曜" 和 "耀" 的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 {
    // 手動(dòng)指定枚舉的值,則可以改變它們的大小順序
    Paperback = 3,
    Hardback = 2,
    Ebook = 1,
}
assert!(BookFormat2::Paperback > BookFormat2::Hardback);
assert!(BookFormat2::Hardback > BookFormat2::Ebook);

對(duì)于字典序比較規(guī)則,還有一些特殊情況,如下:

  • 如果元素A是元素B的前綴,則元素A<元素B;

  • 空字符序列<非空字序列;

2. Ord derive

 #[derive(Ord, Eq, PartialOrd, PartialEq)]
struct Book {
    name: String,
}

#[derive(Ord, Eq, PartialOrd, PartialEq)]
enum BookFormat {
    Paperback,
    Hardback,
    Ebook,
}

這里只需注意一點(diǎn),那就是由于繼承關(guān)系,Ord需要和Eq, PartialOrd, PartialEq同時(shí)派生。另外,根據(jù)前面所提到的,PartialOrd和Ord都支持>=,<=,這個(gè)要記得;

6.5 手動(dòng)實(shí)現(xiàn)PartialOrd和Ord

1. PartialOrd Impl

// 注意這里測(cè)試對(duì)象是Book3,不要為成員字段format即BookFormat3派生任何trait,模擬實(shí)際項(xiàng)目中無(wú)法修改成員字段特性的情況
enum BookFormat3 {
    Paperback,
    Hardback,
    Ebook,
}

struct Book3 {
    name: String,
    format: BookFormat3,
}

// -- 先得實(shí)現(xiàn) PartialEq
impl PartialEq<Self> for Book3 {
    // tips:這里可以將省略
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
        // 這里假設(shè)format字段不要求比較
    }
}

// -- 才能實(shí)現(xiàn) PartialOrd
impl PartialOrd for Book3 {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        // 直接調(diào)用name(String)的比較方法,如果成員字段也沒(méi)有實(shí)現(xiàn)PartialOrd,那就得先為成員實(shí)現(xiàn),這類(lèi)情況很少
        self.name.partial_cmp(&other.name)
    }
}

2. Ord Impl

// 測(cè)試對(duì)象:Book3
// - 這里同樣沒(méi)有使用任何derive,全手動(dòng)實(shí)現(xiàn),由于繼承關(guān)系,需要實(shí)現(xiàn)四個(gè)trait
// - 注意:若存在任一成員字段(這里指   format字段)未實(shí)現(xiàn)PartialEq/Eq/PartialOrd,都是無(wú)法為Book3派生Ord的(派生時(shí)不會(huì)解析下面的手動(dòng)impl)
enum BookFormat3 {
    Paperback,
    Hardback,
    Ebook,
}

struct Book3 {
    name: String,
    format: BookFormat3,
}

// -- 先實(shí)現(xiàn) PartialEq
impl PartialEq for Book3 {
    fn eq(&self, other: &Book3) -> bool {
        self.name == other.name
        // 這里假設(shè)format字段不要求比較
    }
}

// -- 再實(shí)現(xiàn) Eq
impl Eq for Book3 {}

// -- 再實(shí)現(xiàn) Ord
impl Ord for Book3 {
    fn cmp(&self, other: &Book3) -> Ordering {
        // 直接調(diào)用name(String)的cmp方法(當(dāng)需要實(shí)現(xiàn)Ord時(shí),成員字段一般都實(shí)現(xiàn)了Ord,可直接調(diào)用其cmp方法)
        self.name.cmp(&other.name)
    }
}

// -- 最后實(shí)現(xiàn) PartialOrd
impl PartialOrd for Book3 {
    fn partial_cmp(&self, other: &Book3) -> Option<Ordering> {
        // 直接調(diào)用上面實(shí)現(xiàn)的cmp方法
        Some(self.cmp(&other))
    }
}

閱讀此代碼需要注意幾點(diǎn):

  1. 先讀完代碼注釋?zhuān)?/p>

  2. 請(qǐng)注意是先實(shí)現(xiàn)Ord,再實(shí)現(xiàn)PartialOrd,理由是既然一開(kāi)始就想要為類(lèi)型實(shí)現(xiàn)Ord,說(shuō)明類(lèi)型是能夠得出一個(gè)肯定結(jié)果的(非None),所以后實(shí)現(xiàn)PartialOrd直接調(diào)用Ord的cmp()

6.6 比較不同的類(lèi)型

這一節(jié)不貼代碼了,留給讀者去實(shí)現(xiàn)。具體實(shí)現(xiàn)手法可參考前面3.5節(jié)或4.5節(jié)中的內(nèi)容。

6.7 Rust基本類(lèi)型如何實(shí)現(xiàn)PartialOrd和Ord

1. PartialOrd impl macro

我們以前面介紹過(guò)的同樣的方式找到cmp.rs中PartialOrd的實(shí)現(xiàn)宏,代碼如下:

mod impls {
    // ... 前面是PartialEq和Eq的宏實(shí)現(xiàn)

    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> {
                   // 注意看,此處是根據(jù)兩個(gè)比較結(jié)果來(lái)得到最終結(jié)果,本質(zhì)上是要求比較的值滿足對(duì)立性(浮點(diǎn)數(shù)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 }
}

這里要注意一下幾點(diǎn):

  1. 代碼中定義的宏partial_ord_impl!是通過(guò)兩個(gè)比較結(jié)果來(lái)得到最終結(jié)果(看注釋?zhuān)?/p>

  2. 這個(gè)宏除了應(yīng)用在了浮點(diǎn)數(shù)類(lèi)型上,還應(yīng)用在了()bool類(lèi)型。浮點(diǎn)數(shù)類(lèi)型不必多說(shuō),單元類(lèi)型是一種單值類(lèi)型用于排序的情況也比較少,為bool類(lèi)型實(shí)現(xiàn)這個(gè)trait的原因是:有時(shí)我們需要對(duì)包含bool類(lèi)型成員的struct或enum進(jìn)行排序,所以需要為其實(shí)現(xiàn)PartialOrd(注意其實(shí)現(xiàn)也是調(diào)用self.cmp());

這里的impl const中的const關(guān)鍵字意味著標(biāo)記這個(gè)trait實(shí)現(xiàn)是編譯時(shí)常量(編譯時(shí)優(yōu)化),以保證運(yùn)行時(shí)不會(huì)有額外開(kāi)銷(xiāo)。這里是因?yàn)?code style="font-family:'ui-monospace', 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;font-size:13.6px;padding:.2em .4em;">fn partial_cmp()的實(shí)現(xiàn)沒(méi)有修改任何數(shù)據(jù)才可以加const,當(dāng)然還有其他要求例如不能使用動(dòng)態(tài)分配的內(nèi)存(例如 Box 或 Vec)、不能調(diào)用非 const 函數(shù)等;

2. Ord impl macro

mod impls {
    // ... 前面是PartialEq/Eq/PartialOrd的宏實(shí)現(xiàn)

    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 }
}

這里需要了解一下幾點(diǎn):

  1. 實(shí)現(xiàn)Ord的時(shí)候需要同時(shí)實(shí)現(xiàn)PartialOrd,不要求實(shí)現(xiàn)的順序。PartialOrd的partial_cmp()內(nèi)部調(diào)用的是Ord的cmp(),理由前面講過(guò);

  2. 同樣為()和bool類(lèi)型實(shí)現(xiàn)了Ord;

  3. 為大部分基本類(lèi)型char usize u8 u16 ...(除了f32、f64) 實(shí)現(xiàn)了Ord;

6.8 為其他類(lèi)型實(shí)現(xiàn)四大compare-trait

這里指的其他類(lèi)型是!不可變借用類(lèi)型可變借用類(lèi)型,具體實(shí)現(xiàn)代碼就在源碼中剛剛看的宏ord_impl!下方,此處就不再贅述。


審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4308

    瀏覽量

    62434
  • 數(shù)據(jù)類(lèi)型

    關(guān)注

    0

    文章

    236

    瀏覽量

    13609
  • Rust
    +關(guān)注

    關(guān)注

    1

    文章

    228

    瀏覽量

    6574

原文標(biāo)題:[Rust筆記](méi) 一文講透Rust中的PartialEq和Eq

文章出處:【微信號(hào):Rust語(yǔ)言中文社區(qū),微信公眾號(hào):Rust語(yǔ)言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何在Rust讀寫(xiě)文件

    見(jiàn)的內(nèi)存安全問(wèn)題和數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。 在Rust,讀寫(xiě)文件是項(xiàng)非常常見(jiàn)的任務(wù)。本教程將介紹如何在Rust讀寫(xiě)文件,包括基礎(chǔ)用法和進(jìn)階用法。
    的頭像 發(fā)表于 09-20 10:57 ?1982次閱讀

    嵌入式操作系統(tǒng)任務(wù)調(diào)度

    嵌入式操作系統(tǒng)最關(guān)鍵的技術(shù)點(diǎn)就在于任務(wù)管理:嵌入式操作系統(tǒng)任務(wù)調(diào)度那么是不是把任務(wù)調(diào)度理解清楚就能輕松應(yīng)對(duì)面試呢?并不是,面試官會(huì)問(wèn)些工程
    發(fā)表于 12-21 06:01

    RUST在嵌入式開(kāi)發(fā)的應(yīng)用是什么

    Rust種編程語(yǔ)言,它使用戶能夠構(gòu)建可靠、高效的軟件,尤其是用于嵌入式開(kāi)發(fā)的軟件。它的特點(diǎn)是:高性能:Rust具有驚人的速度和高內(nèi)存利用率。可靠性:在編譯過(guò)程可以消除內(nèi)存錯(cuò)誤。生
    發(fā)表于 12-24 08:34

    了解傳云基礎(chǔ)知識(shí)

    了解傳云基礎(chǔ)知識(shí)傳云,我們先了解它的定義,首先了解下****
    發(fā)表于 02-25 10:32

    Rust代碼中加載靜態(tài)庫(kù)時(shí),出現(xiàn)錯(cuò)誤 ` rust-lld: error: undefined symbol: malloc `怎么解決?

    我正在 MCUXpresso IDE 創(chuàng)建個(gè)靜態(tài)庫(kù)。我正在使用 redlib 在我的代碼中導(dǎo)入 ` [i]stdlib.h`。它成功地構(gòu)建了個(gè)靜態(tài)庫(kù)。但是,靜態(tài)庫(kù)未定義
    發(fā)表于 06-09 08:44

    介紹藍(lán)牙傳模塊AT指令集,絕對(duì)干貨值得收藏!

    介紹藍(lán)牙傳模塊AT指令集,絕對(duì)干貨值得收藏!藍(lán)牙傳模塊AT 指令 下面是套藍(lán)牙傳模
    發(fā)表于 01-04 15:29 ?41次下載

    Linux內(nèi)核整合對(duì) Rust 的支持

    Linux Plumbers Conference 2022 大會(huì)上舉行了個(gè) Rust 相關(guān)的小型會(huì)議,該會(huì)議討論的大方向大致為:正在進(jìn)行的使 Rust 成為種合適的系統(tǒng)編程語(yǔ)言的
    的頭像 發(fā)表于 09-19 11:06 ?1159次閱讀

    Linux 6.1 攜帶初始Rust代碼發(fā)布

    ? Linux 6.1 攜帶初始 Rust 代碼發(fā)布 Linus Torvalds 剛剛發(fā)布 Linux 6.1為 stable. 該版本整合了 Rust 語(yǔ)言的初始支持. 原文鏈接:?https
    的頭像 發(fā)表于 12-13 14:37 ?606次閱讀

    rust語(yǔ)言基礎(chǔ)學(xué)習(xí): rust的錯(cuò)誤處理

    錯(cuò)誤是軟件不可避免的,所以 Rust些處理出錯(cuò)情況的特性。在許多情況下,Rust 要求你承認(rèn)錯(cuò)誤的可能性,并在你的代碼編譯前采取
    的頭像 發(fā)表于 05-22 16:28 ?2055次閱讀

    串口

    傳:透明傳輸(SerialNet)。即在傳輸過(guò)程,對(duì)外界透明,不管所傳輸?shù)膬?nèi)容、數(shù)據(jù)協(xié)議形式,不對(duì)要傳輸數(shù)據(jù)做任何處 理,只是把需要傳輸?shù)膬?nèi)容當(dāng)成組二進(jìn)制數(shù)據(jù)完美地傳輸?shù)侥康墓?jié)點(diǎn)。相當(dāng)于
    發(fā)表于 05-30 10:23 ?0次下載
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>講</b><b class='flag-5'>透</b>串口<b class='flag-5'>透</b>傳

    工業(yè)場(chǎng)景的AI應(yīng)用

    AI畢竟是個(gè)工具和方法問(wèn)題,它不是目的,而目的是解決制造業(yè)的“質(zhì)量、成本、交付”問(wèn)題。我們要問(wèn)的問(wèn)題是“目的是什么?”,什么工具是合適的-評(píng)價(jià)指標(biāo)來(lái)自于“經(jīng)濟(jì)性”。
    發(fā)表于 06-13 11:14 ?1021次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>講</b><b class='flag-5'>透</b>工業(yè)場(chǎng)景<b class='flag-5'>中</b>的AI應(yīng)用

    RustPin/Unpin詳解

    對(duì)我來(lái)說(shuō),其中之就是在Rust Pin/Unpin 。
    的頭像 發(fā)表于 07-20 11:00 ?832次閱讀

    數(shù)字鑰匙關(guān)鍵技術(shù):UWB(超寬帶)實(shí)現(xiàn)原理

    在之前的文章《超寬帶(UWB)前世今生》,我們從起源、定義、標(biāo)準(zhǔn)、發(fā)展、應(yīng)用等角度概述了UWB技術(shù)。根據(jù)UWB的特性,其基礎(chǔ)功能分
    的頭像 發(fā)表于 09-08 14:02 ?3423次閱讀
    數(shù)字鑰匙關(guān)鍵技術(shù):UWB(超寬帶)實(shí)現(xiàn)原理<b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>講</b><b class='flag-5'>透</b>

    Rust重寫(xiě)基礎(chǔ)軟件的實(shí)踐

    Rust 語(yǔ)言。本文的主要目的是通過(guò)記錄此次轉(zhuǎn)化過(guò)程遇到的比較常見(jiàn)且有意思的問(wèn)題以及解決此問(wèn)題的方法與大家起做相關(guān)的技術(shù)交流和討論。
    的頭像 發(fā)表于 01-25 11:21 ?597次閱讀

    百度智能云千帆大模型平臺(tái)全面升級(jí)!

    】百度智能云千帆大模型平臺(tái)全面升級(jí)!
    的頭像 發(fā)表于 03-22 10:44 ?459次閱讀
    <b class='flag-5'>一</b>圖<b class='flag-5'>講</b><b class='flag-5'>透</b>百度智能云千帆大模型平臺(tái)全面升級(jí)!