在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一文講透Rust中的PartialEq和Eq

jf_wN0SrCdH ? 來源:Rust語言中文社區 ? 2023-03-07 09:43 ? 次閱讀

前言

本文將圍繞對象:PartialEq和Eq,以及PartialOrd和Ord,即四個Rust中重點的Compare Trait進行討論并解釋其中的細節,內容涵蓋理論以及代碼實現。

在正式介紹PartialEq和Eq、以及PartialOrd和Ord之前,本文會首先介紹它們所遵循的數學理論,也就是相等關系。文章主要分三大部分,第一部分是第1節,討論的是數學中的相等關系;第二部分是第2~5節,主要討論PartialEq和Eq;第三部分是第6節,主要討論PartialOrd和Ord。內容描述可能具有先后順序,建議按章節順序閱讀。

聲明

本文內容來自作者個人的學習成果總結及整理,可能會存在因個人水平導致的表述錯誤,歡迎并感謝讀者指正!

  • 作者:Leigg

  • 首發地址: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年03月03日

  • License:CC-BY-NC-SA 4.0 (轉載請注明作者及來源)

1. 數學中的相等關系

在初中數學中,會介紹到什么是相等關系(也叫等價關系),相等關系是一種基本的二元關系,它描述了兩個對象之間的相等性質。它必須滿足如下三個性質:

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

  • 對稱性:若有a=b,則有b=a

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

也就是說,滿足這三個性質才叫滿足(完全)相等關系。這很容易理解,就不過多解釋。

1.1 部分相等關系

對于簡單的整數類型、字符串類型,我們可以說它們具有完全相等關系,因為它們可以全方位比較(包含兩個維度,第一個是類型空間中的任意值,第二個是每個值的任意成員屬性), 但是對于某些類型就不行了,這些類型總是不滿足其中一個維度。下面一起來看看:

以字符串為例,全方位比較的是它的每個字節值以及整個字符串的長度。

0. 浮點數類型

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

NaN定義于IEEE 754-2008標準的5.2節“特殊值”(Special Values)中,除了NaN,另外兩個特殊值是正無窮大(+infinity)、負無窮大(-infinity),不過這兩個值滿足自反性。

除了浮點數類型,數學中還有其他類型也不具有通常意義上的全等關系,比如集合類型、函數類型。

1. 集合類型

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

在實際應用中,由我們定義(Impl)了一種集合中的特殊相等關系,稱為"集合的相等",這個特殊關系(實現邏輯)中,我們只要求兩個集合的元素相同,不要求其他。

2. 函數類型

首先從浮點數的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)了一種函數中的特殊相等關系,稱為函數的相等。這個特殊關系(實現邏輯)中,我們只要求兩個函數執行結果的值相同,不要求函數執行過程相同。

1.2 部分相等與全相等的關系

部分相等是全相等關系的子集,也就是說,如果兩個元素具有相等關系,那它們之間也一定有部分相等關系。這在編程語言中的實現也是同樣遵循的規則。

1.3 小結

數學中定義了(全)相等關系(等價關系)的三大性質,分別是自反性、對稱性和傳遞性;但某些數據類型中的值或屬性違背了三大性質,就不能叫做滿足全相等關系, 此時只能為該類型實現部分相等關系。

在部分相等關系中,用于比較的值也是滿足三大性質的,因為此時我們排除了那些特殊值。另外,部分相等是全相等關系的子集。

2. 編程與數學的關系

數學是一門研究數據、空間和變化的龐大學科,它提供了一種嚴謹的描述和處理問題的方式,而編程則是將問題的解決方法轉化為計算機程序的過程,可以說,數學是問題的理論形式, 編程則是問題的代碼形式,編程解決問題的依據來自數學。

所以說,編程語言的設計中也是大量運用了數學概念與模型的,本文關注的相等關系就是一個例子。

在Rust庫中的PartialEq的注釋文檔中提到了partial equivalence relations即部分相等關系這一概念,并且同樣使用了浮點數的特殊值NaN來舉例說明。

Eq的注釋文檔則是提到了equivalence relations,并且明確說明了,對于滿足Eqtrait的類型,是一定滿足相等關系的三大性質的。

3. PartialEq

3.1 trait定義

Rust中的PartialEq的命名明確地表達了它的含義,但如果我們忘記了數學中的相等關系,就肯定會對此感到疑惑。先來看看它的定義:

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

在這個定義中,可以得到三個基本信息:

  1. 這個trait包含2個方法,eq和ne,且ne具有默認實現,使用時開發者只需要實現eq方法即可(庫文檔也特別說明,若沒有更好的理由,則不應該手動實現ne方法);

  2. PartialEq綁定的Rhs參數類型是?Size,即包括動態大小類型(DST)和固定大小類型(ST)類型(Rhs是主類型用來比較的類型);

  3. Rhs參數提供了默認類型即Self(和主類型一致),但也可以是其他類型,也就是說,實踐中你甚至可以將i32與struct進行比較,只要實現了對應的PartialEq

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

3.2 對應操作符

這個比較簡單,PartialEq和Eq一致,擁有的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是引用其他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,所以可以比較相等性
}

3.5 比較不同的類型

根據上面的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
    }
}

3.6 Rust基本類型如何實現PartialEq

上文說過,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。如果你還不了解宏,可以暫且理解其是一種編寫重復模式代碼規則的編程特性,它可以減少大量重復代碼。

4. Eq

理解了PartialEq,那Eq理解起來就非常簡單了,本節的內容主體與PartialEq基本一致,所以相對簡明。

4.1 trait定義

如下:

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

根據代碼可以得到兩個重要信息:

  1. Eq是繼承自PartialEq的;

  2. Eq相對PartialEq只多了一個方法assert_receiver_is_total_eq(),并且有默認實現;

第一個,既然Eq繼承自PartialEq,說明想要實現Eq,必先實現PartialEq。第二個是這個assert_receiver_is_total_eq()方法了,簡單來說,它是被derive語法內部使用的,用來斷言類型的每個屬性都實現了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都是使用==!=操作符,無差別感知。

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

6.1 與PartialEq和Eq的關系

很多時候,當我們談到PartialEq和Eq時,PartialOrd和Ord總是不能脫離的話題,因為它們都是一種二元比較關系,前兩者是相等性比較,后兩者是有序性(也可稱大小性)比較。前兩者使用的操作符是==!=,后兩者使用的操作符是>=<,沒錯,PartialOrd和Ord的比較結果是包含等于的,然后我們可以基于這個有序關系來對數據進行排序(sort)。

重點:有序性包含相等性。

與PartialEq存在的原因一樣,PartialOrd的存在的理由也是因為有一些類型是不具有有序性關系的(無法比較),比如浮點數、Bool、Option、函數、閉包等類型。

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

我們可以將PartialOrd和Ord直譯為偏序和全序關系,因為這確實是它們要表達的含義。偏序和全序的概念來自離散數學,下文詳解。

6.2 基本性質

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

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

  • 對立性:若有a,則b>a

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

  • 確定性:必定存在>==<其中的一個關系;

6.3 trait定義

1. PartialOrd trait

// 二元關系定義(<,==,>)
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,這很好理解,無法比較大小的類型也一定不能進行相等性比較;

  2. 提供partial_cmp()方法用于主類型和可以是其他類型的參數比較,返回的Option,表示兩者關系可以是無法比較的(None),那么這里我們就可以聯想到Ord trait返回的肯定是Ordering(因為具有全序的類型不會存在無法比較的情況);

  3. 另外四個方法分別實現了對應的操作符:<,<=,>,>=,即實現了PartialOrd的類型可以使用這些操作符進行比較;除此之外,由于繼承了PartialEq,所以還允許使用==,!=

請再次記住,不管是PartialOrd還是Ord,都包含相等關系。

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與參數other的二元關系,返回Ordering類型(區別于PartialOrd.partial_cmp()返回的Option);

  2. Ord繼承自Eq+PartialOrd,這也很好理解,具有全序關系的類型自然具有偏序關系;

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

  4. 額外提供clamp()方法返回輸入的參數區間內的值;

  5. 顯然,由于繼承了PartialOrd,所以實現了Ord的類型可以使用操作符<,<=,>,>=,==,!=

Self: ~ const Destruct的解釋:位于where后即是類型約束,這里約束了Self類型必須是實現了Destructtrait的一個指向常量的裸指針。

全序和偏序的概念(來自離散數學)

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

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

  • 顯然,全序關系必是偏序關系。反之不成立。

6.4 可派生

1. PartialOrd derive

PartialOrd和Ord也是可以使用derive宏進行自動實現的,代碼如下:

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

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

這里有幾點需要注意:

  1. 由于繼承關系,所以必須同時派生PartialEq;

  2. 與PartialEq相比,不支持為union類型派生;

  3. 對struct進行派生時,大小順序依據的是成員字段的字典序(字母表中的順序,數字與字母比較則根據ASCII表編碼,數字編碼<字母編碼;若比較多字節字符如中文,則轉Unicode編碼后再比較; 實際上ASCII表中的字符編碼與對應Unicode編碼一致);

  4. 對enum進行派生時,大小順序依據的是枚舉類型的值大小,默認情況下,第一個枚舉類型的值是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. Ord derive

 #[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都支持>=,<=,這個要記得;

6.5 手動實現PartialOrd和Ord

1. PartialOrd Impl

// 注意這里測試對象是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)
    }
}

2. Ord Impl

// 測試對象: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))
    }
}

閱讀此代碼需要注意幾點:

  1. 先讀完代碼注釋;

  2. 請注意是先實現Ord,再實現PartialOrd,理由是既然一開始就想要為類型實現Ord,說明類型是能夠得出一個肯定結果的(非None),所以后實現PartialOrd直接調用Ord的cmp()

6.6 比較不同的類型

這一節不貼代碼了,留給讀者去實現。具體實現手法可參考前面3.5節或4.5節中的內容。

6.7 Rust基本類型如何實現PartialOrd和Ord

1. PartialOrd impl macro

我們以前面介紹過的同樣的方式找到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 }
}

這里要注意一下幾點:

  1. 代碼中定義的宏partial_ord_impl!是通過兩個比較結果來得到最終結果(看注釋);

  2. 這個宏除了應用在了浮點數類型上,還應用在了()bool類型。浮點數類型不必多說,單元類型是一種單值類型用于排序的情況也比較少,為bool類型實現這個trait的原因是:有時我們需要對包含bool類型成員的struct或enum進行排序,所以需要為其實現PartialOrd(注意其實現也是調用self.cmp());

這里的impl const中的const關鍵字意味著標記這個trait實現是編譯時常量(編譯時優化),以保證運行時不會有額外開銷。這里是因為fn partial_cmp()的實現沒有修改任何數據才可以加const,當然還有其他要求例如不能使用動態分配的內存(例如 Box 或 Vec)、不能調用非 const 函數等;

2. Ord impl macro

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

這里需要了解一下幾點:

  1. 實現Ord的時候需要同時實現PartialOrd,不要求實現的順序。PartialOrd的partial_cmp()內部調用的是Ord的cmp(),理由前面講過;

  2. 同樣為()和bool類型實現了Ord;

  3. 為大部分基本類型char usize u8 u16 ...(除了f32、f64) 實現了Ord;

6.8 為其他類型實現四大compare-trait

這里指的其他類型是!不可變借用類型可變借用類型,具體實現代碼就在源碼中剛剛看的宏ord_impl!下方,此處就不再贅述。


審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 函數
    +關注

    關注

    3

    文章

    4372

    瀏覽量

    64290
  • 數據類型
    +關注

    關注

    0

    文章

    237

    瀏覽量

    13838
  • Rust
    +關注

    關注

    1

    文章

    233

    瀏覽量

    6975

原文標題:[Rust筆記] 一文講透Rust中的PartialEq和Eq

文章出處:【微信號:Rust語言中文社區,微信公眾號:Rust語言中文社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦
    熱點推薦

    如何在Rust讀寫文件

    見的內存安全問題和數據競爭問題。 在Rust,讀寫文件是項非常常見的任務。本教程將介紹如何在Rust讀寫文件,包括基礎用法和進階用法。
    的頭像 發表于 09-20 10:57 ?2411次閱讀

    嵌入式操作系統任務調度

    嵌入式操作系統最關鍵的技術點就在于任務管理:嵌入式操作系統任務調度那么是不是把任務調度理解清楚就能輕松應對面試呢?并不是,面試官會問些工程
    發表于 12-21 06:01

    RUST在嵌入式開發的應用是什么

    Rust種編程語言,它使用戶能夠構建可靠、高效的軟件,尤其是用于嵌入式開發的軟件。它的特點是:高性能:Rust具有驚人的速度和高內存利用率。可靠性:在編譯過程可以消除內存錯誤。生
    發表于 12-24 08:34

    了解傳云基礎知識

    了解傳云基礎知識傳云,我們先了解它的定義,首先了解下****
    發表于 02-25 10:32

    Rust代碼中加載靜態庫時,出現錯誤 ` rust-lld: error: undefined symbol: malloc `怎么解決?

    我正在 MCUXpresso IDE 創建個靜態庫。我正在使用 redlib 在我的代碼中導入 ` [i]stdlib.h`。它成功地構建了個靜態庫。但是,靜態庫未定義
    發表于 06-09 08:44

    介紹藍牙傳模塊AT指令集,絕對干貨值得收藏!

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

    Linux內核整合對 Rust 的支持

    Linux Plumbers Conference 2022 大會上舉行了Rust 相關的小型會議,該會議討論的大方向大致為:正在進行的使 Rust 成為種合適的系統編程語言的
    的頭像 發表于 09-19 11:06 ?1343次閱讀

    Linux 6.1 攜帶初始Rust代碼發布

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

    rust語言基礎學習: rust的錯誤處理

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

    串口

    傳:透明傳輸(SerialNet)。即在傳輸過程,對外界透明,不管所傳輸的內容、數據協議形式,不對要傳輸數據做任何處 理,只是把需要傳輸的內容當成組二進制數據完美地傳輸到目的節點。相當于
    發表于 05-30 10:23 ?1次下載
    <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>傳

    工業場景的AI應用

    AI畢竟是個工具和方法問題,它不是目的,而目的是解決制造業的“質量、成本、交付”問題。我們要問的問題是“目的是什么?”,什么工具是合適的-評價指標來自于“經濟性”。
    發表于 06-13 11:14 ?1327次閱讀
    <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>的AI應用

    RustPin/Unpin詳解

    對我來說,其中之就是在Rust Pin/Unpin 。
    的頭像 發表于 07-20 11:00 ?1216次閱讀

    數字鑰匙關鍵技術:UWB(超寬帶)實現原理

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

    Rust重寫基礎軟件的實踐

    Rust 語言。本文的主要目的是通過記錄此次轉化過程遇到的比較常見且有意思的問題以及解決此問題的方法與大家起做相關的技術交流和討論。
    的頭像 發表于 01-25 11:21 ?902次閱讀

    百度智能云千帆大模型平臺全面升級!

    】百度智能云千帆大模型平臺全面升級!
    的頭像 發表于 03-22 10:44 ?716次閱讀
    <b class='flag-5'>一</b>圖<b class='flag-5'>講</b><b class='flag-5'>透</b>百度智能云千帆大模型平臺全面升級!
    主站蜘蛛池模板: 亚洲国产成人最新精品资源 | 黄色小视频免费 | 呦交小u女国产秘密入口 | 婷婷六月丁香午夜爱爱 | 美女在线看永久免费网址 | 最近国语视频免费观看在线播放 | 免费黄视频在线观看 | 夜间免费视频 | 婷婷久久综合九色综合九七 | 超级乱淫视频播放日韩 | 天堂网在线最新版www中文网 | 国产精品久久久久久久9999 | 亚洲男人a天堂在线2184 | 国产片一级| 亚洲精品久久久久影 | 日本韩国做暖暖小视频 | 天天做天天爱天天爽综合区 | 花怜write. as| 欧美com| 日本xxxxx黄区免费看动漫 | 日本一区二区免费看 | 欧美激情综合亚洲五月蜜桃 | 天天摸夜夜操 | 色六月婷婷 | 欧美亚洲天堂 | 精品国产一二三区 | 国产香蕉久久精品综合网 | 四虎精品影院4hutv四虎 | 国产亚洲美女 | 亚洲第九页 | 99热久久精品最新 | 国产日本特黄特色大片免费视频 | 美女黄色毛片免费看 | 四虎网址在线 | 九九热在线观看 | 色视频网站人成免费 | 成人综合在线观看 | 亚洲欧美在线一区二区 | 国产乱码一区二区三区四川人 | video另类蛇交 | 国产精品美女一区二区三区 |