[Rust] This article explains PartialEq and Eq in Rust

foreword

This article will discuss and explain the details around the objects: PartialEq and Eq, and PartialOrd and Ord, the four key Compare Traits in Rust, covering theory and code implementation.

Before formally introducing PartialEq and Eq, and PartialOrd and Ord, this article will first introduce the mathematical theory they follow, that is, the equality relationship .
The article is mainly divided into three parts. The first part is section 1, which discusses the equality relationship in mathematics; the second part is section 2~5, which mainly discusses PartialEq and Eq; the third part is section 6, which mainly discusses PartialOrd and Ord. The content description may have a sequential order, it is recommended to read in chapter order.

statement

The content of this article comes from the summary and collation of the author's personal learning results, and there may be errors in expression caused by personal level. Readers are welcome and grateful for corrections!

  • Author: Leigg
  • Starting address: https://github.com/chaseSpace/rust_practices/blob/main/blogs/about_eq_ord_trait.md
  • CSDN:https://blog.csdn.net/sc_lilei/article/details/129322616
  • Release time: March 3, 2023
  • License: CC-BY-NC-SA 4.0 (Please indicate the author and source for reprint)

1. Equivalence in mathematics

In junior high school mathematics, what is an equality relationship (also called an equivalence relationship) will be introduced. The equality relationship is a basic binary relationship, which describes the equality between two objects. It must satisfy the following three properties:

  • Reflexivity (reflexivity): self must be equal to self, ie a=a;
  • Symmetry: if any a=b, then b=a;
  • Transitivity: if there are a=bsums b=c, then there are a=c;

In other words, satisfying these three properties is called satisfying the (complete) equality relationship. This is easy to understand, so I won't explain too much.

1.1 Partial equality relationship

For simple integer types, string types, we can say that they have a complete equality relationship, because they can be compared in all directions (including two dimensions, the first is any value in the type space, and the second is the value of each value arbitrary member properties), but not for some types, which always do not satisfy one of the dimensions
. Let's take a look together:

Taking a string as an example, the comprehensive comparison is the length of each byte value and the entire string.

0. Floating point type

There is a special value in the floating-point type is NaN (Not-a-number), which is not equal to any value (including itself), which directly violates reflexivity. At this time, we need to define a partial equality relationship for floating-point numbers, which is mainly for comparing non-NaN floating-point numbers.

NaN is defined in Section 5.2 "Special Values" of the IEEE 754-2008 standard. In addition to NaN, the other two special values ​​are positive infinity (+infinity) and negative infinity (-infinity), but these two values ​​satisfy the self- rebellion.

In addition to floating point types, there are other types in mathematics that do not have the usual congruence relationship, such as collection types and function types.

1. Collection type

Suppose there are sets A={1,2,3}, B={1,3,2}, then are A and B equal or unequal at this time? This needs to be looked at from different perspectives. When we only care about whether the collection contains the same elements, we can say that they are equal. When we also strictly require the order of the elements to be consistent, they are not equal.

In practical applications, we define (Impl) a special equality relationship in sets, called "set equality". In this special relationship (implementation logic), we only require that the elements of two sets are the same, and do not require other.

2. Function type

First look at functions from the perspective of NaN of floating point numbers. Suppose there are functions A=f(x) and B=f(y). If x=y, then obviously the value of A is also equal to B, but if there is a parameter z is nothing Meaning, it means that f(z) has no result or the result is illegal, so can it be said that f(z) is equal to itself at this time?
That obviously won't work. This example has the same meaning as the floating-point example.

Then look at the function again from the perspective of the collection type. Suppose there are functions A=f(x) and B=g(x). Note that they are two different functions. When the two are given the same input x to produce the same result, At this time, are f(x) and g(x) equal or not?
Similar to sets, in practical applications, we also define (Impl) a special equality relationship in functions, called equality of functions. In this special relationship (implementation logic), we only require that the values ​​of the execution results of the two functions are the same, and we do not require the execution process of the functions to be the same.

1.2 The relationship between partial equality and total equality

Partial equality is a subset of full equality, that is, if two elements have an equality relationship, there must also be a partial equality relationship between them. The implementation of this in programming languages ​​also follows the same rules.

1.3 Summary

Mathematics defines the three major properties of the (full) equality relationship (equivalent relationship), which are reflexivity, symmetry and transitivity; however, the values ​​or attributes in some data types violate the three major properties, so they cannot be called Satisfy the full equality relationship, at this time only partial equality relationship can be realized for this type.

In the partial equality relationship, the values ​​used for comparison also satisfy the three properties , because at this time we exclude those special values. Also, partial equality is a subset of full equality relations.

2. The relationship between programming and mathematics

Mathematics is a huge subject that studies data, space and change. It provides a rigorous way to describe and deal with problems, while programming is the process of converting the solution to a problem into a computer program. It can be said that mathematics is a problem The theoretical form of programming is the code form of the problem, and the basis for solving the problem comes from mathematics.

Therefore, a large number of mathematical concepts and models are used in the design of programming languages. The equality relationship that this article focuses on is an example.

The concept of partial equivalence relationsPartialEq is mentioned in the comment document in the Rust library , and the special value NaN of floating point numbers is also used as an example.

EqThe comment document of the document mentions equivalence relations , and clearly states that for Eqa type that satisfies a trait, it must satisfy the three properties of the equality relation.

3. PartialEq

3.1 trait definition

The naming of PartialEq in Rust clearly expresses its meaning, but if we forget about the equality relationship in mathematics, we must be confused about it. Let's take a look at its definition first:

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

In this definition, three basic pieces of information can be obtained:

  1. This trait contains two methods, eq and ne, and ne has a default implementation. When using it, developers only need to implement the eq method (the library documentation also specifically states that if there is no better reason, the ne method should not be implemented manually) ;
  2. The Rhs parameter type of PartialEq binding is ?Size, that is, including dynamic size type (DST) and fixed size type (ST) type (Rhs is the type used by the main type for comparison);
  3. The Rhs parameter provides the default type Self(consistent with the main type), but it can also be other types , that is to say, in practice, you can even i32compare with struct, as long as the corresponding one is implemented PartialEq;

The lhs and rhs in Rust refer to the "left-hand side" (left-hand side) and "right-hand side" (right-hand side) parameters.

3.2 Corresponding operators

This is relatively simple, PartialEq is consistent with Eq, and has eq and ne methods corresponding ==to !=two operators. Most of Rust's basic types such as integers, floats, strings, etc. implement PartialEq, so they can use ==and !=for equality comparisons.

3.3 Derivable

deriveThe English description is Derivable, which means that PartialEq can be automatically implemented for custom composite types (struct/enum/union types) through macros, and the usage is as follows:

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

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

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

It should be noted that the premise of derivation is that all member fields under this composite type support PartialEq. The following code illustrates this situation:

// #[derive(PartialEq)]  // 取消注释即可编译通过
enum BookFormat {
    
     Paperback, Hardback, Ebook }

// 无法编译!!!
#[derive(PartialEq)]
struct Book {
    
    
    name: String,
    format: BookFormat, // 未实现PartialEq
}

Extension: Use cargo expandthe command to print out the PartialEq code implemented by the macro for the type.

3.4 Manually implement PartialEq

The above code is taken as an example. We assume BookFormatthat the code under other crates cannot be added to it (it cannot be modified). In this case, BookPartialEq needs to be implemented manually. The code is as follows:

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 Comparing different types

According to the trait definition above, we know that as long as different types of Rhs parameters are associated when implementing PartialEq, we can compare the equality of different types. The sample code is as follows:

#[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);  // 无法反过来比较
}

It should be noted that only the equality comparison between Car and Wheel is implemented in the code snippet. If the comparison is reversed, the reverse implementation must be provided, as follows:

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

3.6 How Rust basic types implement PartialEq

As mentioned above, the basic types of Rust all implement PartialEq, so how is it implemented? Do you write a set of impl codes for each type? Where is the code?

If you use an IDE, you can hold down the ctrl key anywhere (depending on the IDE) and click in the code PartialEqto open its code file in the standard library cmp.rs, the relative path is RUST_LIB_DIR/core/src/cmp.rs.
In this file you can find the following macro code:

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

The powerful macro feature of Rust is used here (the declaration macro is used here, which is fairly simple), to quickly implement the PartialEq trait for many basic types of Rust. If you don't know macros yet, it's a programming feature for writing rules for repeating pattern code, which can reduce a lot of repetitive code.

4. Eq

After understanding PartialEq, Eq is very simple to understand. The main content of this section is basically the same as PartialEq, so it is relatively concise.

4.1 trait definition

as follows:

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

According to the code, two important information can be obtained:

  1. Eq is inherited from PartialEq;
  2. Eq has only one more method than PartialEq assert_receiver_is_total_eq(), and has a default implementation;

First, since Eq inherits from PartialEq, it means that if you want to implement Eq, you must first implement PartialEq. The second is this assert_receiver_is_total_eq()
method. Simply put, it is used internally by the derive syntax to assert that each attribute of the type implements the Eq feature. For us users, we don’t need to pay too much attention.

4.2 Corresponding operators

There is no difference from PartialEq, slightly.

4.3 Derivable

Similar to the use of PartialEq, just note that when deriving, due to the inheritance relationship, Eq and PartialEq must exist at the same time.

#[derive(PartialEq, Eq)] // 顺序无关
struct Book {
    
    
    name: String,
}

4.4 Manual implementation of Eq

Look directly at the code:

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

It should be noted that PartialEq must be implemented first, and then Eq. In addition, what can be seen here is that in terms of comparing equality, both Eq and PartialEq use the ==AND !=operator, and there is no difference in perception.

4.5 Comparing different types

There is no difference from PartialEq, slightly.

4.6 How Rust basic types implement Eq

Like PartialEq, in RUST_LIB_DIR/core/src/cmp.rsthe file with a relative path of , there is the following macro code:

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. Tests on floating point numbers

At present, in the standard library, the author only finds that there are floating-point numbers that only implement PartialEq (and composite types that contain floating-point numbers). The following is the test code for floating-point numbers:

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. Partial Ord and Ord

6.1 Relationship with PartialEq and Eq

Many times, when we talk about PartialEq and Eq, PartialOrd and Ord are always inseparable from the topic, because they are both a binary comparison relationship, the first two are equality comparisons, and the latter two are orderliness (also can be called size) comparison. The operators used by the first two are ==and !=
, and the operators used by the latter two are >, , =
, <yes, the comparison result of PartialOrd and Ord contains equals , and then we can sort the data based on this ordered relationship (sort ).

Important: Ordering includes equality.

Like the reason for the existence of PartialEq, the reason for the existence of PartialOrd is also because there are some types that do not have an order relationship (cannot be compared), such as floating point numbers, Bool, Option, functions, closures and other types.

PartialEq and Eq, PartialOrd and Ord together describe any type of binary comparison relationship in Rust, including equality and order. So in the above, you may also observe that the definitions of PartialOrd and Ord are also located cmp.rsin the file.

We can literally translate PartialOrd and Ord into partial order and total order relationship, because this is indeed what they mean. The concepts of partial order and total order come from discrete mathematics, which are explained in detail below.

6.2 Basic properties

PartialOrd and Ord also satisfy certain basic properties, and PartialOrd satisfies:

  • Transitivity: If yes a<b, b<c, then a<c. and >is ==the same as;
  • Contrast: if yes a<b, then b>a;

Ord is based on PartialOrd, which naturally follows transitivity and opposition. In addition, for any two elements, it also satisfies the following properties:

  • Certainty : there must exist >or one of the relationships;==<

6.3 trait definition

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

Basic Information:

  1. PartialOrd inherits from PartialEq, which is well understood. Types that cannot be compared in size must not be compared for equality;
  2. Provide partial_cmp()a method for comparing the main type with parameters that can be other types, and return it Option<Ordering>, indicating that the relationship between the two can be incomparable (None), so here we can think that
    the return of the Ord trait must be Ordering(because it has a total order Types will not be incomparable);
  3. The other four methods implement the corresponding operators: <, <=, >, >=, that is, types that implement PartialOrd can use these operators for comparison; in addition, because they inherit PartialEq, they are also allowed to use ==;!=

Remember again that both PartialOrd and Ord contain equality relations.

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

Basic Information:

  1. cmpThe method is used to compare the binary relationship between self and parameters other, and the return Orderingtype (different from that returned by PartialOrd.partial_cmp() Option<Ordering>);
  2. Ord inherits from Eq+PartialOrd, which is also well understood. Types with a total order relationship naturally have a partial order relationship;
  3. Provide min/max()methods to return otherthe smaller/larger value between self and the argument;
  4. Additional clamp()methods are provided to return values ​​within the input parameter range;
  5. Obviously, since it inherits from PartialOrd, the type that implements Ord can use the operator <, <=, >, >=, ==, !=;

Correct Self: ~ const Destructexplanation: after where is the type constraint, where the constraint type must be a naked pointer to a constant that Selfimplements the trait.Destruct

The concept of total order and partial order (from discrete mathematics)

  • Total order: that is, the total order relationship, which is naturally a binary relationship. Total order refers to the relationship between any two elements in the set that can be compared. For example, any two numbers in real numbers can be compared in size, then "size" is a total order relationship of the real number set.
  • Partial order: A relationship in which only some of the elements in a collection can be compared. For example, not all numbers in the complex number set can be compared in size, then "size" is a partial order relationship of the complex number set.
  • Obviously, a total order must be a partial order. The opposite is not true.

6.4 Derivable

1. PartialOrd derive

PartialOrd and Ord can also be deriveautomatically implemented using macros, the code is as follows:

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

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

Here are a few points to note:

  1. Due to the inheritance relationship, PartialEq must be derived at the same time;
  2. Compared with PartialEq, unionderivation for type is not supported;
  3. When deriving a struct, the size order is based on the dictionary order of the member fields (the order in the alphabet, the comparison between numbers and letters is based on the ASCII table encoding, and the number encoding<letter encoding; if comparing multi-byte characters such as Chinese, then switch to Compare after Unicode encoding;
    in fact, the character encoding in the ASCII table is consistent with the corresponding Unicode encoding);
  4. When deriving enum, the size order is based on the value size of the enumeration type. By default, the value of the first enumeration type is 1, and it is incremented downward by 1, so the first enumeration is the smallest;

The following uses the code to illustrate points 2 and 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);

For lexicographical comparison rules, there are some special cases, as follows:

  • If element A is a prefix of element B, then element A<element B;
  • empty character sequence < non-empty word sequence;

2. Ord derive

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

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

Just one thing to note here is that due to the inheritance relationship, Ord needs to be derived from Eq, PartialOrd, and PartialEq at the same time. In addition, according to the above mentioned, both PartialOrd and Ord support >=, <=this should be remembered;

6.5 Manually implement PartialOrd and 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:这里可以将<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. 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))
    }
}

A few things to note when reading this code:

  1. Read the code comments first;
  2. Please note that Ord is implemented first, and then PartialOrd is implemented. The reason is that since you want to implement Ord for the type from the beginning, it means that the type can get a positive result (not None), so you can directly call Ord after implementing PartialOrd cmp();

6.6 Comparing different types

This section does not post the code, leaving it to the reader to implement. The specific implementation method can refer to the content in the previous section 3.5 or 4.5.

6.7 How Rust basic types implement PartialOrd and Ord

1. PartialOrd impl macro

We find the implementation macro of PartialOrd in the same way as described above cmp.rs, the code is as follows:

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

Here are a few points to note:

  1. The macro defined in the code partial_ord_impl!obtains the final result through two comparison results (see comments);
  2. This macro not only applies to floating-point types, but also to ()sum booltypes. Needless to say, the floating-point number type, the unit type is a single-value type used for sorting is relatively rare, the reason for implementing this trait for the bool type is: sometimes
    we need to sort the struct or enum containing bool type members, so PartialOrd needs to be implemented for it (note that its implementation is also called self.cmp());

The const keyword here impl constmeans to mark this trait implementation as a compile-time constant (compile-time optimization) to ensure that there will be no additional overhead at runtime. This is because fn partial_cmp()the implementation does not modify any data before it can be added const. Of course, there are other requirements such as not being able to use dynamically allocated memory (such as Box or Vec), not being able to call non-const functions, etc.;

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

Here are a few things to know:

  1. When implementing Ord, you need to implement PartialOrd at the same time, and the order of implementation is not required. The internal part of PartialOrd partial_cmp()calls Ord cmp(), the reason mentioned earlier;
  2. Ord is also ()implemented for bool types;
  3. char usize u8 u16 ...Ord is implemented for most primitive types ;

6.8 Implement the four compare-traits for other types

The other types referred to here are !, , 不可变借用类型, 可变借用类型and the specific implementation code is just below the macro just seen in the source code ord_impl!, so I won’t go into details here.


Guess you like

Origin blog.csdn.net/sc_lilei/article/details/129322616