Comparing Values

Test for Equality with PartialEq and Eq

std

The std::cmp module provides traits for comparing values and implementing algorithms that require ordering or equality checks.

  • PartialEq↗ is used for types that can be checked for equality (using == and !=).
  • Eq↗ is a marker trait that indicates a type has a reflexive equality relation, meaning a == a is always true. It requires PartialEq to be implemented first.
  • Floats (like f32 and f64) do not implement Eq, because NaN != NaN. Beware when implementing equality on structs with a field of float type.

PartialEq and Eq are most often automatically derived using #[derive(PartialEq, Eq)] - see Derive. You can however provide a custom implementation, if so desired:


// `#[derive(PartialEq, Eq)]` automatically implements equality traits.
// When derived on structs, two instances are equal if all fields are equal,
// and not equal if any fields are not equal.
// When derived on enums, two instances are equal if they are the same variant
// and all fields are equal.
#[derive(Debug, PartialEq, Eq)]
struct Point {
    x: i32,
    y: i32,
}

fn points() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 1, y: 2 };
    let p3 = Point { x: 3, y: 4 };

    println!("p1 == p2: {}", p1 == p2); // true
    println!("p1 == p3: {}", p1 == p3); // false
}

#[derive(Debug)]
struct Person {
    id: u64,
    last_name: String,
}

// Custom implementation of `PartialEq` and `Eq`:
impl PartialEq for Person {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
        // `last_name` is ignored.
    }
}

// `Eq` has no methods.
impl Eq for Person {}

fn people() {
    let p1 = Person {
        id: 1,
        last_name: "Smith".to_string(),
    };
    let p2 = Person {
        id: 2,
        last_name: "Smith".to_string(),
    };
    assert_ne!(p1, p2);
}

fn main() {
    points();
    people();
}

Compare and Sort Values with PartialOrd and Ord

std

PartialOrd↗ is used for types that can be compared for ordering, but may not have a total order.

Ord↗ is used for types that have a total order, meaning every pair of values can be compared. Ord requires that the implementing type also be PartialOrd, PartialEq, and Eq.

Both traits can be automatically implemented with #[derive(...)]. When writing a custom implementation, it is recommended to read the documentation for Ord and PartialOrd to avoid logic errors: Ord must be consistent with the PartialOrd implementation, and PartialOrd with PartialEq.

Sorting algorithms often rely on the PartialOrd and Ord trait to determine the order of elements. The standard library provides several sorting functions that use these traits, such as sort() and sort_by() on slices.

The following example implements a custom order for software versions:

use std::cmp::Ordering;

// A struct to represent a software version.
// The `Eq` trait has no methods, thus we can derive it automatically.
#[derive(Debug, Eq)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

// Implement `PartialEq` to define when two versions are equal.
impl PartialEq for Version {
    fn eq(&self, other: &Self) -> bool {
        self.major == other.major
            && self.minor == other.minor
            && self.patch == other.patch
    }
}

// Implement `PartialOrd`. Since our ordering is total, `partial_cmp`
// can simply wrap the result of `cmp` in `Some`.
impl PartialOrd for Version {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// Implement `Ord` to define the total ordering of versions.
// It implements a lexicographical comparison.
impl Ord for Version {
    fn cmp(&self, other: &Self) -> Ordering {
        // Compare major versions first.
        match self.major.cmp(&other.major) {
            Ordering::Equal => {
                // If major versions are equal, compare minor versions.
                match self.minor.cmp(&other.minor) {
                    Ordering::Equal => {
                        // If minor versions are also equal, compare patch
                        // versions.
                        self.patch.cmp(&other.patch)
                    }
                    // If minor versions differ, that's our result.
                    other_ordering => other_ordering,
                }
            }
            // If major versions differ, that's our result.
            other_ordering => other_ordering,
        }
    }
}

fn main() {
    let v1 = Version {
        major: 1,
        minor: 2,
        patch: 3,
    };
    let v2 = Version {
        major: 1,
        minor: 3,
        patch: 0,
    };
    let v3 = Version {
        major: 2,
        minor: 0,
        patch: 0,
    };

    // We can sort a collection of versions:
    let mut versions = vec![v3, v1, v2];
    println!("Before sorting: {versions:?}");

    versions.sort(); // `sort()` requires the `Ord` trait.

    println!("After sorting: {versions:?}");

    // The sorted order should be [1.2.3, 1.3.0, 2.0.0].
    assert_eq!(
        versions[0],
        Version {
            major: 1,
            minor: 2,
            patch: 3
        }
    );
    assert_eq!(
        versions[1],
        Version {
            major: 1,
            minor: 3,
            patch: 0
        }
    );
    assert_eq!(
        versions[2],
        Version {
            major: 2,
            minor: 0,
            patch: 0
        }
    );
}
  • Algorithms.
  • Data Structures.
  • Derive.
  • Generics.
  • Sorting.
  • Traits.

References