rust overloaded comparison operators

To overload a comparison operator, you need to implement the corresponding trait for the type.
Overloading == and != requires implementing PartialEq or Eq
Overloading <, <=, >, >= requires implementing PartialOrd or Ord

1. Eq/PartialEq

Why are there two traits?
Because there are two types of equality relationships: one is complete equality, and the other is partial equality.
The perfect equality relationship satisfies the following three properties:
Reflexivity: Oneself must be equal to oneself, that is, a=a;
Symmetry: If there is a=b, then there is b=a;
Transitivity: If there are a=b and b=c, then there is a=c;

Partial equality relations only satisfy two properties:
Symmetry: If there is a=b, then there is b=a;
Transitivity: If there are a=b and b=c, then there is a=c;

There is a special value in the floating-point number type called NaN (Not-a-number). This value is not equal to any value, including its own NaN != NaN, which violates reflexivity. Therefore, only partial equality relationships can be used to determine whether floating point numbers are equal.

Partial equality is a subset of the full equality relationship. That is to say, if two elements have a full equality relationship, there must also be a partial equality relationship between them.

(1) PartialEq

It is a partial equality relationship.
Two methods are defined in this Trait:

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

eq: If the two values are equal, it returns true and needs to be reloaded by the user.
ne: Returns true if the two values are not equal. It has been implemented by default.

All basic types implement PartialEq

1. Implement PartialEq for custom types
Can be automatically implemented by the compiler using #[derive(PartialEq)]

 #[derive(PartialEq)]
 pub struct Person {
     pub id: u32,
     pub name: String,
     pub height: f64,
}

You can also do it manually

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

When implementing, you only need to implement the eq method. For ne, we use the default one.

After implementing PartialEq, the == and != operators are automatically overloaded. You can then determine whether they are equal.

fn main() {
     let p1 = Person {
         id: 0,
         name: "John".to_string(),
         height: 1.2,
     };
     let p2 = Person {
         id: 0,
         name: "Jack".to_string(),
         height: 1.4,
     };
     println!("p1 == p2 = {}", p1 == p2); // p1 == p2 = true
}

example

fn main() {
     let f1 = f32::NAN;
     let f2 = f32::NAN;
     if f1 == f2 {
         println!("NaN can be compared, this is not mathematical!")
     } else {
         println!("Sure enough, although both are NaN, they are not actually equal")
     }
}

2. Compare different types
By passing different types into Rhs, you 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( & amp;self, other: & amp;WheelBrand) -> bool {<!-- -->
self.brand == *other
     }
}
fn main() {<!-- -->
     let car = Car {<!-- --> brand: WheelBrand::Benz, price: 10000 };
     let wheel = WheelBrand::Benz; // Compare struct and enum
     assert!(car == wheel);
     // assert!(wheel == car); // Cannot compare in reverse
}

The code only implements the equality comparison between Car and Wheel. If you want to compare in reverse, you must provide the reverse implementation, as follows:

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

(2) Eq

It’s a completely equal relationship

This trait inherits PartialEq, but does not add new methods. This Trait just tells the compiler that this is a complete equality relationship rather than a partial equality relationship.

pub Trait Eq:PartialEq{}

In the standard library, only f32 and f64 do not implement Eq

1. Implement Eq for custom types
No additional code is required to implement Eq. You only need to implement PartialEq and add #[derive(Eq)].

Types that implement Eq naturally also overload the == and != operators. You can determine whether they are equal below.
The key of HashMap in Rust requires the implementation of Eq, that is, it must be completely equal. Since floating point numbers do not implement Eq, they cannot be used for the key of HashMap.

2. Ord / PartialOrd

There are also two types of large and small relationships: one is a total order relationship and the other is a partial order relationship.
The total order relationship has the following properties:

Total antisymmetry of complete asymmetry: a < b, a == b, a > b. Only one of the three results is true;
Transitivity transitive: if a < b and b < c then a < c;

The partial order relationship has the following properties:

Asymmetry antisymmetry: if a < b then !(a > b);
Transitivity transitive: if a < b and b < c then a < c;

Or is it because of the special value NaN, NaN < 0 == false and NaN > 0 == false and (NaN == 0) == false

(1) PartialOrd

partial order relation

PartialOrd inherits PartialEq and defines several new methods

pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {
     fn partial_cmp( & amp;self, other: & amp;Rhs) -> Option<Ordering>;
     fn lt( & amp;self, other: & amp;Rhs) -> bool {
         matches!(self.partial_cmp(other), Some(Less))
     }
     fn le( & amp;self, other: & amp;Rhs) -> bool {
         matches!(self.partial_cmp(other), Some(Less | Equal))
     }
     fn gt( & amp;self, other: & amp;Rhs) -> bool {
         matches!(self.partial_cmp(other), Some(Greater))
     }
     fn ge( & amp;self, other: & amp;Rhs) -> bool {
         matches!(self.partial_cmp(other), Some(Greater | Equal))
     }
}

partial_cmp: requires user overloading and returns the comparison result of two values;
lt, le, gt, ge: defined by default;

All basic types in the standard library have implemented this Trait

1. Implement PartialOrd for custom types
To implement PartialOrd, PartialEq must also be implemented

You can use the #[derive(PartialOrd)] method to implement this Trait, or you can implement it manually.

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

To implement PartialOrd, you only need to implement the partial_cmp method. lt(), le(), gt(), ge() use the default ones.

The comparison result is the Ordering enumeration type:

pub enum Ordering {
     Less = -1,
     Equal = 0,
     Greater = 1,
}

Why is the return value type of the partial_cmp method an Option, rather than directly an Ordering value type?
This is still related to the floating point type, because NaN is not a representable number, and expressions such as: 3.0 < NaN are meaningless! In this case, partial_cmp will return None.
partial_cmp returns an Option resulting in a result. When the result is None, the ordering of the two values cannot be determined, that is, x and y will be in an indeterminate order. Implementing PartialOrd alone is not enough to make your custom type sortable, you also need to implement Ord.

Types that implement PartialOrd will automatically overload the <, <=, >, >= operators, which can be compared below.
example

fn main() {
     let p1 = Person {
         id: 0,
         name: "John".to_string(),
         height: 1.2,
     };
     let p2 = Person {
         id: 0,
         name: "Jack".to_string(),
         height: 1.4,
     };
     println!("p1 < p2 = {}", p1 < p2);
     println!("p1 <= p2 = {}", p1 <= p2);
     println!("p1 > p2 = {}", p1 > p2);
     println!("p1 >= p2 = {}", p1 >= p2);
     let x: f64 = std::f64::NAN;
     let y = 1.0f64;
     assert_eq!(x.partial_cmp( & amp;y), None);
}

2. Compare different types

(二)Ord

total order relation
Ord inherits PartialOrd and Eq, and several new methods are defined:

pub trait Ord: Eq + PartialOrd<Self> {
     fn cmp( & amp;self, other: & amp;Self) -> Ordering;
     fn max(self, other: Self) -> Self{...}
     fn min(self, other: Self) -> Self{...}
     fn clamp(self, min: Self, max: Self) -> Self{...}
}

cmp: The user needs to overload this method to return the comparison result of the two values;
max, min, clamp: already defined;

In the standard library, only f32 and f64 do not implement Ord

1. Implement Ord for custom types
To implement Ord, you must implement PartialOrd and Eq at the same time. When implementing PartialEq, PartialOrd and Ord, special attention should be paid to not conflicting with each other.
example
The sort method in vector requires that the type implements Ord

use std::cmp::Ordering;
#[derive(Debug,Eq)]
pub struct Person {
     pub id: u32,
     pub name: String,
     pub height: f64,
}
impl PartialEq<Self> for Person {
     fn eq( & amp;self, other: & amp;Self) -> bool { self.id == other.id }
}
impl PartialOrd for Person {
     fn partial_cmp( & amp;self, other: & amp;Self) -> Option<Ordering> {
         self.id.partial_cmp( & amp;other.id)
     }
}
impl Ord for Person {
     fn cmp( & amp;self, other: & amp;Self) -> Ordering {
         self.id.cmp(&other.id)
     }
}

To implement Ord, you only need to implement the cmp method, and use the default ones for max() and min().

example

enum BookFormat3 {
    Paperback,
    Hardback,
    Ebook,
}

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

// -- Implement PartialEq first
impl PartialEq for Book3 {
    fn eq( & amp;self, other: & amp;Book3) -> bool {
        self.name == other.name
        // It is assumed here that the format field does not require comparison
    }
}

// -- Then implement Eq
impl Eq for Book3 {}

// -- Then implement Ord
impl Ord for Book3 {
    fn cmp( & amp;self, other: & amp;Book3) -> Ordering {
        // Directly call the cmp method of name(String) (when it is necessary to implement Ord, the member fields generally implement Ord, and its cmp method can be directly called)
        self.name.cmp(&other.name)
    }
}

// --Finally implement PartialOrd
impl PartialOrd for Book3 {
    fn partial_cmp( & amp;self, other: & amp;Book3) -> Option<Ordering> {
        // Directly call the cmp method implemented above
        Some(self.cmp( & amp;other))
    }
}

When implementing Ord, you need to implement PartialOrd at the same time. PartialOrd’s partial_cmp() internally calls Ord’s cmp(). The reason is that you want to implement Ord for the type from the beginning. , indicating that the type can produce a positive result (not None)

After Ord is implemented, the <, <=, >, >= operators will be automatically overloaded, and you can compare them below.
example

use std::cmp::Ordering;
let x = 1;
let y = 2;
assert_eq!(x.cmp( & amp;y), Ordering::Less);
assert_eq!(y.cmp( & amp;x), Ordering::Greater);
assert_eq!(x.cmp( & amp;x), Ordering::Equal);
assert_eq!((-3).clamp(-2, 1), -2);
assert_eq!(0.clamp(-2, 1), 0);
assert_eq!(2.clamp(-2, 1), 1);
assert_eq!(1.min(2), 1);
assert_eq!(2.min(2), 2);
assert_eq!(1.max(2), 2);
assert_eq!(2.max(2), 2);

example

fn main() {
     let mut v = vec![
         Person {
             id: 3,
             name: "".to_string(),
             height: 3.0,
         },
         Person {
             id: 2,
             name: "".to_string(),
             height: 4.0,
         },
         Person {
             id: 1,
             name: "".to_string(),
             height: 5.0,
         },
     ];
     v.sort();
     println!("{:?}", v);
}

2. Compare different types

3. About efficiency

You may be curious, is it more efficient to implement it manually or auto-derive? This is difficult to say and cannot be generalized. For example: if you know for sure that only a few members of a large struct are directly related to it, or decisive, then your own manually implemented version is likely to be better than the auto-derive version. Because the auto-derive version usually compares all members in sequence, it is likely to be useless. To put it another way, although the auto-derive version may check and compare each struct member in turn, because it can adopt the short-circuit principle of Boolean expressions, it may stop checking the first member, so it is likely to be faster than itself. Define implementation version. But Hash is an exception. It does not allow the short-circuit principle. All members must be hashed once in sequence. You cannot be lazy, but if you can only hash 1 or 2 simple members instead of a large number of string members, then you will It’s easy to defeat the auto-derive default implementation.