Comparison & Ordering (PartialEq, Ord)

Reviewed & published by Brayan K

Comparison in Rust is powered by four traits — PartialEq and Eq give you ==, while PartialOrd and Ord give you < and the ability to sort.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll derive these traits, sort with sort, sort_by, and sort_by_key, use cmp and Ordering, find min/max, and see why floats are only PartialOrd.

What You'll Learn in This Lesson

1️⃣ Deriving the Comparison Traits

You rarely write comparison logic by hand. A #[derive(...)] attribute tells the compiler to generate it: PartialEq/Eq for ==, and PartialOrd/Ord for <, >, and friends. Derived ordering compares fields in declaration order.

// Deriving these traits lets the compiler generate == and < for your type.
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
struct Version {
    major: u32,
    minor: u32,
}

fn main() {
    let a = Version { major: 1, minor: 2 };
    let b = Version { major: 1, minor: 5 };

    // == comes from PartialEq/Eq.
    println!("a == b: {}", a == b);
    println!("a != b: {}", a != b);

    // < <= > >= come from PartialOrd/Ord; fields compared in declaration order.
    println!("a < b:  {}", a < b);

    // Ord also gives min and max.
    println!("max = {:?}", a.max(b));
}

Because major is declared before minor, two versions are first compared by major; only when those tie does minor break the tie — exactly how version numbers should sort.

2️⃣ Sorting: sort, sort_by, sort_by_key

Once a type is Ord, sort() orders a slice ascending in place. For custom orders, sort_by takes a comparator closure, and sort_by_key sorts by a key you extract from each element. Iterators also offer min and max.

fn main() {
    let mut nums = vec![5, 2, 9, 1, 7];

    // sort() needs Ord; it sorts ascending in place.
    nums.sort();
    println!("sorted = {:?}", nums);

    // sort_by with a custom comparator (here: descending).
    nums.sort_by(|a, b| b.cmp(a));
    println!("desc   = {:?}", nums);

    // sort_by_key sorts by a derived key — here, distance from 5.
    let mut more = vec![1, 8, 4, 6, 5];
    more.sort_by_key(|&x| (x as i32 - 5).abs());
    println!("by key = {:?}", more);

    // min / max on iterators.
    println!("min = {:?}", nums.iter().min());
    println!("max = {:?}", nums.iter().max());
}

3️⃣ cmp, Ordering & the Float Catch

The cmp method returns an Ordering — one of Less, Equal, or Greater — which you can match on. Floats only implement the Partial traits, so they use partial_cmp, which returns an Option<Ordering> because NaN is unordered.

use std::cmp::Ordering;

fn main() {
    // cmp returns an Ordering: Less, Equal, or Greater.
    let result = 3.cmp(&7);
    println!("3.cmp(&7) = {:?}", result);

    match 10.cmp(&10) {
        Ordering::Less => println!("less"),
        Ordering::Equal => println!("equal"),
        Ordering::Greater => println!("greater"),
    }

    // Floats are only PartialOrd because NaN is unordered: use partial_cmp.
    let x = 1.5_f64;
    let y = 2.5_f64;
    println!("1.5.partial_cmp(&2.5) = {:?}", x.partial_cmp(&y));

    // NaN compares as None — not Less, Equal, or Greater.
    let nan = f64::NAN;
    println!("NaN.partial_cmp(&1.0) = {:?}", nan.partial_cmp(&1.0));
    println!("NaN == NaN: {}", nan == nan);
}

Your turn. Fill in the blanks marked ___, then run it.

// 🎯 YOUR TURN — fill in the blanks, then run it.
fn main() {
    let mut scores = vec![88, 72, 95, 60];

    // 1) Sort the vec ascending in place.
    scores.___();
    println!("sorted = {:?}", scores);

    // 2) Get the largest element from an iterator.
    println!("highest = {:?}", scores.iter().___());

    // 3) Compare two numbers, getting an Ordering back.
    let cmp = 4.___(&9);
    println!("4 vs 9 = {:?}", cmp);
}

// ✅ Expected output:
//    sorted = [60, 72, 88, 95]
//    highest = Some(95)
//    4 vs 9 = Less

Mini-Challenge: A Leaderboard

Sort a list of players by score, highest first, and print a ranked list. Run it with cargo run and check the output.

// 🎯 MINI-CHALLENGE: a leaderboard.
//
// Define a struct Player { name: String, score: u32 } (derive Debug).
// Build a Vec of three players, then:
// 1. Sort them by score DESCENDING.
//    Hint: sort_by_key(|p| std::cmp::Reverse(p.score)) sorts high-to-low.
// 2. Print a ranked list: "1. Name - score", "2. ...", using .enumerate().
//
// ✅ Example output:
//    1. Bo - 95
//    2. Cy - 82
//    3. Ada - 70

fn main() {
    // define Player, build the Vec, sort, and print here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Comparison & Ordering

ToolGives you
PartialEq / Eq== and !=
PartialOrd / Ord< <= > >=, sort
a.cmp(&b)Ordering (Ord)
a.partial_cmp(&b)Option (floats)
v.sort_by_key(..)Sort by extracted key
Reverse(key)Descending order

🎉 Lesson Complete!

Practice quiz

Which traits give you the == and != operators?

  • PartialOrd / Ord
  • Display / Debug
  • PartialEq / Eq
  • Clone / Copy

Answer: PartialEq / Eq. PartialEq provides == and !=; Eq is a marker that strengthens equality to be total.

Which traits give you < <= > >= and the ability to sort?

  • PartialOrd / Ord
  • PartialEq / Eq
  • Hash
  • Default

Answer: PartialOrd / Ord. PartialOrd provides the ordering operators; Ord adds a total order needed for sort().

In what order does derived Ord compare a struct's fields?

  • Alphabetically by name
  • By field size
  • Randomly
  • In declaration order (top to bottom)

Answer: In declaration order (top to bottom). Derived Ord compares fields lexicographically in the order they are declared.

What does this print? #[derive(PartialEq,Eq,PartialOrd,Ord)] struct V { major: u32, minor: u32 } let a = V{major:1,minor:9}; let b = V{major:2,minor:0}; println!("{}", a < b);

  • false
  • true
  • It does not compile
  • Equal

Answer: true. major is compared first; 1 < 2, so a < b is true regardless of minor.

What does this print? let mut more = vec![1, 8, 4, 6, 5]; more.sort_by_key(|&x| (x as i32 - 5).abs()); println!("{:?}", more);

  • [5, 4, 6, 8, 1]
  • [1, 4, 5, 6, 8]
  • [8, 6, 5, 4, 1]
  • [5, 6, 4, 8, 1]

Answer: [5, 4, 6, 8, 1]. Sorting by distance from 5 gives keys 4,3,1,1,0 -> order 5,4,6,8,1.

What does 3.cmp(&7) return?

  • Ordering::Greater
  • Ordering::Equal
  • Ordering::Less
  • None

Answer: Ordering::Less. 3 is less than 7, so cmp returns Ordering::Less.

Why are f64 and f32 only PartialOrd and not Ord?

  • They are too large
  • Because NaN is unordered, so no total order exists
  • Because they are unsigned
  • They are Ord, actually

Answer: Because NaN is unordered, so no total order exists. NaN is neither less, equal, nor greater than anything, breaking the total order Ord requires.

What does NaN == NaN evaluate to?

  • true
  • It panics
  • None
  • false

Answer: false. By IEEE rules NaN is not equal to itself, so NaN == NaN is false.

What does f64::NAN.partial_cmp(&1.0) return?

  • Some(Less)
  • None
  • Some(Greater)
  • Some(Equal)

Answer: None. NaN is unordered, so partial_cmp returns None rather than an Ordering.

What is the cleanest way to sort descending without a custom comparator?

  • sort().reverse() on keys
  • sort_desc()
  • sort_by_key with std::cmp::Reverse around the key
  • It is impossible

Answer: sort_by_key with std::cmp::Reverse around the key. Wrapping the key in std::cmp::Reverse flips the order for a clean descending sort.

Continue this course

Frequently asked questions

What's the difference between PartialEq and Eq, or PartialOrd and Ord?

PartialEq provides == and != for a type. Eq is a marker that adds no methods but promises equality is total and reflexive — that every value equals itself. PartialOrd provides the ordering operators < <= > >= and the partial_cmp method that returns an Option, allowing for incomparable values. Ord strengthens that to a total order via cmp, which returns a plain Ordering rather than an Option. Integers and strings implement all four, so they can be sorted directly; floats implement only the Partial versions, which is why floats cannot be sorted with the simple sort method.

Why are floats only PartialOrd and not Ord?

Floating-point numbers include the special value NaN (not-a-number), and NaN is unordered: it is neither less than, equal to, nor greater than anything, and it isn't even equal to itself. A total order, which Ord requires, can't exist when some values are incomparable, so f32 and f64 implement only PartialEq and PartialOrd. In practice this means you can't call sort directly on a Vec of floats. You either compare with partial_cmp and handle the NaN case, or use total_cmp, or sort with sort_by(|a, b| a.partial_cmp(b).unwrap()) when you're sure no NaNs are present.

How do I make my own struct sortable?

Add a derive attribute above the struct, like #[derive(PartialEq, Eq, PartialOrd, Ord)], and the compiler generates the comparison logic for you. For ordering, derived Ord compares the fields in the order they're declared, lexicographically — the first field is the primary sort key, the next breaks ties, and so on. If you need different logic, implement Ord by hand or just sort with sort_by_key, passing a closure that returns the key you want to order by. Remember a type needs Ord to use the plain sort method.

When should I use sort, sort_by, or sort_by_key?

Use sort when the elements already implement Ord and you want the natural ascending order — it's the simplest. Use sort_by when you need a custom comparison, passing a closure that takes two elements and returns an Ordering, for example |a, b| b.cmp(a) to sort descending. Use sort_by_key when each element maps to a sort key; it calls your closure once per element to extract the key, which is cleaner and often faster than recomputing in a comparator. A handy trick for descending order is sort_by_key with std::cmp::Reverse around the key.