Ordering

#170 May 2026

170. Ordering::then_with — Chain Comparators for Multi-Key Sorts

Sorting by name, then by age, then by id ends in a nested if a == b { ... } ladder. Ordering::then_with flattens the whole thing into one expression.

The pain: a manual tie-break ladder grows ugly fast.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
items.sort_by(|a, b| {
    let ord = a.last.cmp(&b.last);
    if ord != std::cmp::Ordering::Equal {
        ord
    } else {
        let ord = a.first.cmp(&b.first);
        if ord != std::cmp::Ordering::Equal {
            ord
        } else {
            a.id.cmp(&b.id)
        }
    }
});

Ordering has two combinators built for exactly this:

  • then(other) — eager: returns self if it’s not Equal, otherwise other.
  • then_with(|| other) — lazy: only computes the next comparison if the previous was Equal.

Prefer then_with whenever the next cmp does real work (string comparison, derived keys), so you don’t pay for it on every pair.

1
2
3
4
5
items.sort_by(|a, b| {
    a.last.cmp(&b.last)
        .then_with(|| a.first.cmp(&b.first))
        .then_with(|| a.id.cmp(&b.id))
});

Reads top-to-bottom in priority order, no nesting, no early-return. Mixing ascending and descending is a one-character change — wrap a field in Reverse:

1
2
3
4
5
6
7
8
use std::cmp::Reverse;

// Last name ascending, then age DESCENDING, then id ascending.
items.sort_by(|a, b| {
    a.last.cmp(&b.last)
        .then_with(|| Reverse(a.age).cmp(&Reverse(b.age)))
        .then_with(|| a.id.cmp(&b.id))
});

The same pattern works in any Ord/PartialOrd impl — then_with is how a hand-written cmp stays readable.