How to Use std

:cmp for Ordering and Comparison

Use std::cmp::Ordering with .cmp() and match to compare values and handle less, greater, or equal cases.

When booleans aren't enough

You are building a leaderboard for a game. You have a list of players, each with a name and a score. You want to sort the list so the highest scores appear first. You write players.sort() and hit a wall. The compiler rejects the code with E0277 (trait bound not satisfied). Your Player struct has no idea how to compare itself to another Player.

Rust does not guess how to order your data. It refuses to sort unless you prove the data can be compared. This is where std::cmp enters the picture. The module provides the tools to define how values relate to each other. At the center is std::cmp::Ordering, the type that captures the full result of a comparison.

Booleans hide information. A comparison that returns true or false tells you whether a condition holds, but it obscures the relationship. If a < b is false, you still don't know if a is greater than b or equal to b. Sorting algorithms need the complete picture. They need to know the direction and magnitude of the difference in a single step. Ordering gives them that map.

The Ordering enum

std::cmp::Ordering is an enum with three variants: Less, Equal, and Greater. It represents the result of comparing two values. Think of a balance scale. You place value a on the left pan and value b on the right pan. The scale tips left if a is lighter, tips right if a is heavier, or stays balanced if they weigh the same.

This three-way result is more powerful than a boolean. It allows algorithms to make decisions without redundant checks. When you implement sorting, the algorithm calls your comparison function once and gets the full relationship. It can place the element in the correct position immediately.

The Ordering enum lives in std::cmp. The trait that provides the .cmp() method is Ord, which is in the prelude. This distinction trips up beginners. You can call .cmp() without importing anything because the trait is available by default. However, the return type Ordering is not in the prelude. If you pattern match on the result, you must import std::cmp::Ordering or use the fully qualified path. Forgetting this import is a common rite of passage.

Minimal example

The .cmp() method is defined on the Ord trait. Most primitive types like integers and strings implement Ord. You can call .cmp() on them directly. The method takes a reference to the other value and returns an Ordering.

use std::cmp::Ordering;

fn main() {
    let a = 5;
    let b = 10;

    // .cmp() returns an Ordering enum.
    // The result depends on the relationship between a and b.
    match a.cmp(&b) {
        Ordering::Less => println!("{a} is less than {b}"),
        Ordering::Greater => println!("{a} is greater than {b}"),
        Ordering::Equal => println!("{a} is equal to {b}"),
    }
}

The match statement handles all three variants. Rust's exhaustiveness checking ensures you cannot forget a case. If you omit Ordering::Equal, the compiler warns you about a non-exhaustive pattern. This safety prevents logic errors where equality is ignored.

Booleans are a leaky abstraction for sorting. Ordering closes the leak by forcing you to handle every outcome.

The trait hierarchy

Comparison in Rust is structured as a hierarchy of traits. Each trait adds guarantees and methods. Understanding the hierarchy helps you implement comparison correctly for custom types.

PartialEq provides the == and != operators. It returns a boolean. It allows for types where equality might not be reflexive or transitive. Floating-point numbers implement PartialEq because NaN == NaN is false.

Eq extends PartialEq with the guarantee that equality is reflexive and transitive. If a == b and b == c, then a == c. Also, a == a must always be true. Types that implement Eq have no "weird" equality behavior. Hash maps and sets require Eq to function correctly.

PartialOrd provides the <, >, <=, and >= operators. It returns Option<Ordering> via the partial_cmp method. The Option allows for types where some values cannot be compared. Floating-point numbers implement PartialOrd because NaN cannot be ordered relative to any value, including itself.

Ord extends PartialOrd and Eq. It provides the .cmp() method, which returns Ordering directly, not wrapped in an Option. Ord requires a total ordering. Every pair of values must be comparable, and the comparison must be consistent with equality. If you implement Ord, you promise that your type has no incomparable states.

The hierarchy is a contract. Ord sits at the top, demanding the strongest guarantees. When you derive or implement Ord, you must also satisfy PartialOrd, Eq, and PartialEq. The compiler enforces this chain.

Respect the trait bounds. If the compiler demands Eq, your type must be reflexive. No exceptions.

Realistic example: Custom sorting

Custom structs do not implement Ord by default. You must implement it manually or use #[derive(Ord)]. Deriving works field-by-field in declaration order. If you need a custom sort order, such as sorting by score first and then by name, you implement Ord yourself.

The idiomatic pattern for multi-field comparison is chaining with then_with. This method returns the current ordering if it is not Equal, or evaluates a closure to compute the next comparison if it is Equal. It avoids nested if blocks and reads like a specification.

use std::cmp::Ordering;

#[derive(Debug, PartialEq, Eq)]
struct Player {
    name: String,
    score: u32,
}

impl Ord for Player {
    /// Compare players by score descending, then by name ascending.
    fn cmp(&self, other: &Self) -> Ordering {
        // Compare scores. Reverse the order so higher scores come first.
        other.score.cmp(&self.score)
            // If scores are equal, compare names alphabetically.
            .then_with(|| self.name.cmp(&other.name))
    }
}

impl PartialOrd for Player {
    /// Delegate partial comparison to the total ordering.
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

The cmp implementation compares scores first. Notice the arguments are swapped: other.score.cmp(&self.score). This reverses the order, placing higher scores at the beginning of a sorted list. The then_with call chains the name comparison. It only runs if the scores are equal.

The PartialOrd implementation delegates to Ord. This is the standard pattern for types that are always comparable. It keeps the logic in one place and ensures consistency. If you ever change cmp, partial_cmp updates automatically.

The community prefers then_with for chaining over nested if blocks. It reads like a specification and compiles to efficient code.

Pitfalls and compiler errors

Comparison code often hits trait bound errors or logic traps. Knowing the common pitfalls saves debugging time.

Floating-point types do not implement Ord. f32 and f64 only implement PartialOrd. The presence of NaN breaks total ordering. NaN is not equal to itself, violating Eq. NaN is not less than, greater than, or equal to any value, violating total order. If you try to sort a slice of f64, the compiler rejects it with E0277 (trait bound not satisfied). You must handle NaN explicitly or use a wrapper type that defines a convention for ordering.

Forgetting Eq and PartialEq is another trap. Ord requires these traits. If you derive Ord but miss Eq, the compiler stops you with E0277. The error message points to the missing trait. Deriving all four traits together avoids this.

Using cmp on types that only implement PartialOrd causes E0599 (no method named cmp found). This happens with floats or custom types that have incomparable states. You must use partial_cmp instead, which returns Option<Ordering>. Unwrapping the result blindly risks a panic if the values are incomparable.

The Ordering enum is not in the prelude. Using Ordering::Less without an import triggers E0433 (failed to resolve). Add use std::cmp::Ordering; to fix it. This import is required even though the Ord trait is available by default.

Convention aside: Rc::clone(&data) vs data.clone() is a common style discussion, but for Ordering, the convention is clear. Always import Ordering explicitly. It signals intent and avoids confusion with other enums that might have Less or Greater variants in third-party crates.

Counter-intuitive but true: the more complex your comparison logic, the more likely you are to violate transitivity. If a < b and b < c but a > c, your sort will panic or produce garbage. Test your Ord implementation with edge cases.

Choosing the right tool

Rust provides multiple ways to compare values. Pick the one that matches your needs.

Use cmp when you need the full relationship between two values, such as sorting a collection or implementing Ord. Use partial_cmp when your type can have incomparable states, like floating-point numbers with NaN or a custom enum with an Unknown variant. Use the < and > operators when you only need a boolean result and don't care whether the difference is large or small. Use std::cmp::max and std::cmp::min when you want to pick the larger or smaller value directly without branching logic.

Pick the tool that matches your certainty. Total order gets cmp. Uncertainty gets partial_cmp.

Where to go next