std::cmp
The std::cmp
module offers several traits for comparing values. It allows us to check for equality and determine the ordering of values. Unlike some other languages where the compiler automatically provides these implementations, Rust requires us to explicitly implement or derive these traits.
Implementing these traits can sometimes be challenging. In this post, we will explore the PartialEq
, Eq
, PartialOrd
, Ord
, and Reverse
traits in detail.
PartialEq
Many might assume that PartialEq
is termed “partial” because it only compares certain parts of a type. However, the “partial” in PartialEq
actually indicates that the equality comparison does not need to be reflexive (a == a)
. In other words, PartialEq
does not require a type to always be equal to itself.
The PartialEq
trait is defined as follows:
pub trait PartialEq<Rhs = Self>
where
Rhs: ?Sized,
{
// Required method
fn eq(&self, other: &Rhs) -> bool;
// Provided method
fn ne(&self, other: &Rhs) -> bool { ... }
}
The PartialEq
trait requires the implementation of the eq
method, which checks if two values are equal. By default, the ne
method is provided, which checks if two values are not equal. Essentially, eq
corresponds to the ==
operator, while ne
corresponds to the !=
operator.
At first glance, it might seem that the PartialEq
trait is sufficient for determining equality between values. However, from a mathematical standpoint, equality should be reflexive, meaning a value must always be equal to itself.
It may seem intuitive that all types should exhibit reflexive equality, there are exceptions. For instance, the floating-point types f32
and f64
include a special value called NaN
(Not a Number). NaN
is unique in that it is not equal to itself, thus violating the reflexivity property. Consequently, PartialEq
does not enforce reflexivity by default.
According to the IEEE 754 standard, comparisons involving NaN
always return false, even when comparing NaN
with itself. This means that if x
is NaN
, the following comparisons will all return false:
x == x
x < x
x > x
However, the comparison x != x
will return true.
So in some of data structures, like HashSet
and HashMap
, the PartialEq
trait is not sufficient for some of their operations. For example, the contains_key
method of HashMap
requires the Eq
trait, which enforces reflexivity.
pub fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
As a result, it is impossible to create a HashMap
with f32
or f64
keys because they do not implement the Eq
trait.
let mut map: HashMap<f64, String> = HashMap::new();
map.insert(3.14, String::from("Pi"));
map.insert(2.71, String::from("Euler"));
Attempting to compile this code will result in an error because f64
does not satisfy the Eq
trait requirements.
error[E0599]: the method `insert` exists for struct `HashMap<f64, String>`, but its trait bounds were not satisfied
--> src/main.rs:8:9
|
8 | map.insert(3.14, String::from("Pi"));
| ^^^^^^
|
= note: the following trait bounds were not satisfied:
`f64: Eq`
`f64: Hash`
So Rust provides the Eq
trait, which extends the PartialEq
trait by enforcing reflexivity. We will discuss the Eq
trait in more detail later in this post.
To implement the PartialEq
trait, you can either do it manually or use the derive
attribute.
For example, you can derive the PartialEq
trait for a custom type like this:
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
Expanding the derive
attribute generates the following implementation:
struct Point {
x: i32,
y: i32,
}
#[automatically_derived]
impl ::core::marker::StructuralPartialEq for Point {}
#[automatically_derived]
impl ::core::cmp::PartialEq for Point {
#[inline]
fn eq(&self, other: &Point) -> bool {
self.x == other.x && self.y == other.y
}
}
As shown, the PartialEq
trait is implemented for the Point
struct. The eq
method checks if the x
and y
fields of two Point
instances are equal.
For enums, the derive
attribute generates an implementation that checks if the variants are equal:
#[derive(PartialEq)]
enum Direction {
Up,
Down,
Left,
Right,
}
The derive
attribute generates the following implementation:
enum Direction {
Up,
Down,
Left,
Right,
}
#[automatically_derived]
impl ::core::marker::StructuralPartialEq for Direction {}
#[automatically_derived]
impl ::core::cmp::PartialEq for Direction {
#[inline]
fn eq(&self, other: &Direction) -> bool {
let self_discr = ::core::intrinsics::discriminant_value(self);
let other_discr = ::core::intrinsics::discriminant_value(other);
self_discr == other_discr
}
}
You can also implement the PartialEq
trait manually:
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y
}
}
As mentioned earlier, the term “partial” in PartialEq
does not imply that only part of a struct is compared. Moreover, implementing the PartialEq
trait for only some fields of a type is not recommended. Let’s consider the following example.
From the definition of the PartialEq
trait, we can see that it takes a type parameter Rhs
, which defaults to Self
. This means you can use the PartialEq
trait to compare two different types.
// Define clothing types, deriving PartialEq to allow comparisons
#[derive(PartialEq)]
enum ClothingType {
Shirt,
Pants,
Jacket,
}
// Define a Clothing struct with an ID and a clothing type
struct Clothing {
id: i32,
clothing_type: ClothingType,
}
// Allow comparisons between Clothing and ClothingType
impl PartialEq<ClothingType> for Clothing {
fn eq(&self, other: &ClothingType) -> bool {
self.clothing_type == *other
}
}
// Allow comparisons between ClothingType and Clothing
impl PartialEq<Clothing> for ClothingType {
fn eq(&self, other: &Clothing) -> bool {
*self == other.clothing_type
}
}
fn main() {
let my_clothing = Clothing {
id: 101,
clothing_type: ClothingType::Shirt,
};
// Check if the clothing item is a shirt
assert!(my_clothing == ClothingType::Shirt);
// Check if the clothing item is not a jacket
assert!(ClothingType::Jacket != my_clothing);
}
In this example, we define a Clothing
struct that includes an ID and a ClothingType
field. We then implement the PartialEq
trait for both Clothing
and ClothingType
, enabling comparisons between these two types.
However, the above example can be problematic as it may violate the transitivity property of equality. For instance, consider the following code:
// Define clothing types, deriving PartialEq to allow comparisons
#[derive(PartialEq)]
enum ClothingType {
Shirt,
Pants,
Jacket,
}
// Define a Clothing struct with an ID and a clothing type, deriving PartialEq
#[derive(PartialEq)]
struct Clothing {
id: i32,
clothing_type: ClothingType,
}
// Allow comparisons between Clothing and ClothingType
impl PartialEq<ClothingType> for Clothing {
fn eq(&self, other: &ClothingType) -> bool {
self.clothing_type == *other
}
}
// Allow comparisons between ClothingType and Clothing
impl PartialEq<Clothing> for ClothingType {
fn eq(&self, other: &Clothing) -> bool {
*self == other.clothing_type
}
}
fn main() {
let c1 = Clothing {
id: 101,
clothing_type: ClothingType::Shirt,
};
let c2 = Clothing {
id: 102,
clothing_type: ClothingType::Shirt,
};
assert!(c1 == ClothingType::Shirt);
assert!(ClothingType::Shirt == c2);
// Check if the clothing items are equal
assert!(c1 == c2); // This assertion will fail
}
In this scenario, equality comparisons can lead to unexpected results, breaking the transitivity rule. Transitivity requires that if a == b
and b == c
, then a == c
must also hold true. Violating this property can cause logical inconsistencies and bugs in your code.
In most cases, it is advisable to derive the PartialEq
trait for a struct or enum. If you choose to implement the trait manually, be careful to maintain the transitivity property to avoid logical inconsistencies.
Eq
The Eq
trait extends the PartialEq
trait by enforcing reflexivity. It is defined as follows:
pub trait Eq: PartialEq { }
The Eq
trait does not introduce any new methods beyond those in PartialEq
. Instead, it acts as a marker to signify that a type adheres to the reflexivity property of equality. This means that any type implementing Eq
must ensure that every instance of the type is equal to itself.
It’s important to note that the Rust compiler cannot automatically verify the reflexivity property. Therefore, it is the programmer’s responsibility to ensure that the Eq
trait is implemented correctly and that the reflexivity property is upheld.
Similar to the PartialEq
trait, you can derive the Eq
trait for a custom type:
#[derive(Eq, PartialEq)]
struct Point {
x: i32,
y: i32,
}
Note that you must also derive the PartialEq
trait because Eq
depends on it.
PartialOrd
The PartialOrd
trait is used to compare values and determine their ordering. It is defined as follows:
pub trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
where
Rhs: ?Sized,
{
// Required method
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
// Provided methods
fn lt(&self, other: &Rhs) -> bool { ... }
fn le(&self, other: &Rhs) -> bool { ... }
fn gt(&self, other: &Rhs) -> bool { ... }
fn ge(&self, other: &Rhs) -> bool { ... }
}
The lt
, le
, gt
, and ge
methods of this trait allow comparisons using the <
, <=
, >
, and >=
operators, respectively.
The partial_cmp
method returns an Option<Ordering>
enum, which can be Some(Ordering::Less)
, Some(Ordering::Equal)
, Some(Ordering::Greater)
, or None
.
You might wonder why the PartialOrd
trait returns an Option<Ordering>
instead of an Ordering
directly. The reason is similar to the PartialEq
trait: it accounts for cases where values cannot be compared, such as NaN
in floating-point numbers. Thus, partial_cmp
returns None
when a comparison is not possible.
fn main() {
let x = f32::NAN;
let y = 5.0;
assert_eq!(x.partial_cmp(&y), None);
assert_eq!(y.partial_cmp(&x), None);
}
To implement the PartialOrd
trait, you can either do it manually or derive it using the derive
attribute.
For example, you can derive the PartialOrd
trait for a struct like this:
#[derive(PartialEq, PartialOrd)]
struct Point {
x: i32,
y: i32,
}
Expanding the derive
attribute generates the following implementation:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
struct Point {
x: i32,
y: i32,
}
... // Implementation of PartialEq
#[automatically_derived]
impl ::core::cmp::PartialOrd for Point {
#[inline]
fn partial_cmp(&self, other: &Point)
-> ::core::option::Option<::core::cmp::Ordering> {
match ::core::cmp::PartialOrd::partial_cmp(&self.x, &other.x) {
::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
::core::cmp::PartialOrd::partial_cmp(&self.y, &other.y),
cmp => cmp,
}
}
}
The comparison follows a lexicographical order, first comparing the x
fields and then the y
fields top to bottom.
For enums, variants are ordered by their discriminant values:
#[derive(PartialEq, PartialOrd)]
enum Direction {
Up,
Down,
Left,
Right,
}
assert!(Direction::Up < Direction::Down);
The values of the variants are assigned discriminant values by default, starting from 0
for the first variant and incrementing by 1
for each subsequent variant.
You can verify the discriminant values of the variants using the std::mem::discriminant
function:
use std::mem::discriminant;
let up = Direction::Up;
let down = Direction::Down;
println!("{:?}", discriminant(&up)); // Output: Discriminant(0)
println!("{:?}", discriminant(&down)); // Output: Discriminant(1)
The derive
attribute generates the following implementation:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
enum Direction { Up, Down, Left, Right, }
... // Implementation of PartialEq
#[automatically_derived]
impl ::core::cmp::PartialOrd for Direction {
#[inline]
fn partial_cmp(&self, other: &Direction)
-> ::core::option::Option<::core::cmp::Ordering> {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
::core::cmp::PartialOrd::partial_cmp(&__self_discr, &__arg1_discr)
}
}
You can manually set the discriminant values for the variants to change the ordering:
#[derive(PartialEq, PartialOrd)]
enum Direction {
Up = 4,
Down = 3,
Left = 2,
Right = 1,
}
assert!(Direction::Up > Direction::Down);
It is also possible to implement the PartialOrd
trait manually:
struct Point {
x: i32,
y: i32,
}
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y
}
}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.x.partial_cmp(&other.x) {
Some(Ordering::Equal) => self.y.partial_cmp(&other.y),
other => other,
}
}
}
assert!(Point { x: 1, y: 2 } < Point { x: 2, y: 1 });
But be careful when implementing the PartialOrd
trait manually. If you derive the PartialEq
trait for a type, you should also derive the PartialOrd
trait to ensure consistency between equality and ordering comparisons.
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.x.cmp(&other.x))
}
}
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 1 };
// `PartialEq` and `PartialOrd` are inconsistent
assert!(p1.partial_cmp(&p2) == Some(Ordering::Equal));
assert!(p1 == p2); // This assertion will fail
When you choose to implement the PartialOrd
trait manually, it is advisable to implement all related traits (PartialEq
, Eq
, PartialOrd
, and Ord
) consistently and manually to ensure logical coherence.
Ord
The Ord
trait builds upon the PartialOrd
and Eq
traits, ensuring that a type adheres to a total ordering. It is defined as follows:
pub trait Ord: Eq + PartialOrd {
// Required method
fn cmp(&self, other: &Self) -> Ordering;
// Provided methods
fn max(self, other: Self) -> Self
where Self: Sized { ... }
fn min(self, other: Self) -> Self
where Self: Sized { ... }
fn clamp(self, min: Self, max: Self) -> Self
where Self: Sized { ... }
}
The Ord
trait enforces reflexivity through the Eq
trait and provides the cmp
method for comparing two values, returning an Ordering
enum. This ensures that every pair of values can be compared, establishing a total order.
The implementation of the Ord
trait must be consistent with the PartialOrd
trait. Ord
requires that the type also be PartialOrd
, PartialEq
, and Eq
.
You can choose to derive it, or implement it manually. If you derive it, you should derive all four traits. If you implement it manually, you should manually implement all four traits, based on the implementation of Ord
.
struct Point {
x: i32,
y: i32,
}
impl Ord for Point {
fn cmp(&self, other: &Self) -> Ordering {
match self.x.cmp(&other.x) {
Ordering::Equal => self.y.cmp(&other.y),
other => other,
}
}
}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y
}
}
impl Eq for Point {}
When implementing PartialOrd
, you can directly call the cmp
method within the partial_cmp
implementation. This approach ensures consistency between the PartialOrd
and Ord
implementations.
Reverse
In certain situations, you might need to reverse the ordering of a type. For example, when using the BinaryHeap
data structure, you may want to retrieve the smallest element instead of the largest. Rust provides the Reverse
wrapper type to achieve this.
use std::cmp::Reverse;
use std::collections::BinaryHeap;
fn main() {
let mut heap = BinaryHeap::new();
heap.push(Reverse(1));
heap.push(Reverse(3));
heap.push(Reverse(2));
assert_eq!(heap.pop(), Some(Reverse(1)));
assert_eq!(heap.pop(), Some(Reverse(2)));
assert_eq!(heap.pop(), Some(Reverse(3)));
}
When you want to access the value inside the Reverse
wrapper, you can use .0 to access the inner value.
let rev = Reverse(5);
assert_eq!(rev.0, 5);
The Reverse
type is a simple wrapper around a value that implements the Ord
, PartialOrd
, Eq
, and PartialEq
traits. It reverses the ordering of the inner value, allowing you to change the sorting behavior of a type.
pub struct Reverse<T>(pub T);
Since this is all about ordering, there is no difference for PartialEq
and Eq
. However, for PartialOrd
and Ord
, the traits are implemented as follows:
impl<T: PartialOrd> PartialOrd for Reverse<T> {
#[inline]
fn partial_cmp(&self, other: &Reverse<T>) -> Option<Ordering> {
other.0.partial_cmp(&self.0)
}
#[inline]
fn lt(&self, other: &Self) -> bool {
other.0 < self.0
}
#[inline]
fn le(&self, other: &Self) -> bool {
other.0 <= self.0
}
#[inline]
fn gt(&self, other: &Self) -> bool {
other.0 > self.0
}
#[inline]
fn ge(&self, other: &Self) -> bool {
other.0 >= self.0
}
}
impl<T: Ord> Ord for Reverse<T> {
#[inline]
fn cmp(&self, other: &Reverse<T>) -> Ordering {
other.0.cmp(&self.0)
}
}
It simply reverses the order of the comparison. That’s all there is to it!
Conclusion
This was a long post, but I hope it helped you understand the PartialEq
, Eq
, PartialOrd
, Ord
, and Reverse
traits in Rust. Remember to use the derive
attribute whenever possible to avoid inconsistencies.