Skip to content

slice::sort_by

slice methods · Collections

Level: reference · for working programmers

One line: Sort in place by a comparison closure you write — descending order, floats, or several keys at once.

pub fn sort_by<F>(&mut self, compare: F)
where
    F: FnMut(&T, &T) -> Ordering,

Stable since 1.0.0.

The closure gets two references and returns an Ordering. |a, b| a.cmp(b) is what sort does; swap them, |a, b| b.cmp(a), and the slice sorts descending. Stable, like sort.

Three jobs it does that sort cannot:

  • Floats. f64 is not Ord. a.partial_cmp(b).unwrap() works until a NaN arrives, then panics; f64::total_cmp never does, and it is a plain function so it passes without a closure: floats.sort_by(f64::total_cmp).
  • Several keys. Chain with then_with: a.len().cmp(&b.len()).then_with(|| a.cmp(b)) sorts by length, then alphabetically among equal lengths.
  • A field. |a, b| a.0.cmp(&b.0) — though when the comparison is only a key, sort_by_key says so more clearly.

The comparison must be a total order — consistent with itself across every pair — or the sort may panic. A closure that returns Ordering::Equal for NaN and something else for everything else breaks that, which is the second reason to prefer total_cmp.

Example

slice_sort_by.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

use std::cmp::Ordering;

fn main() {
    let mut nums = vec![5, 3, 9, 1];
    nums.sort_by(|a, b| b.cmp(a));
    println!("descending: {nums:?}");

    // Two keys: by length, then alphabetically among equal lengths.
    let mut words = vec!["pear", "fig", "apple", "kiwi", "date"];
    words.sort_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
    println!("by length, then name: {words:?}");

    // Longest first, still alphabetical within a length: reverse only the first key.
    words.sort_by(|a, b| match a.len().cmp(&b.len()) {
        Ordering::Equal => a.cmp(b),
        other => other.reverse(),
    });
    println!("longest first: {words:?}");

    // f64 has no Ord. partial_cmp().unwrap() works until a NaN arrives.
    let mut floats = vec![2.5, -1.0, 0.0];
    floats.sort_by(|a, b| a.partial_cmp(b).unwrap());
    println!("{floats:?}");
    let mut with_nan = vec![2.5, f64::NAN, -1.0];
    with_nan.sort_by(f64::total_cmp);
    println!("{with_nan:?}   <- total_cmp puts NaN last, and never panics");

    // The closure sees references, so a field comparison is a.0.cmp(&b.0).
    let mut pairs = vec![(3, "c"), (1, "a"), (2, "b")];
    pairs.sort_by(|a, b| a.0.cmp(&b.0));
    println!("{pairs:?}");
}

Verified output of slice_sort_by.rs — regenerated by tools/run_examples.py, never hand-typed.

descending: [9, 5, 3, 1]
by length, then name: ["fig", "date", "kiwi", "pear", "apple"]
longest first: ["apple", "date", "kiwi", "pear", "fig"]
[-1.0, 0.0, 2.5]
[-1.0, 2.5, NaN]   <- total_cmp puts NaN last, and never panics
[(1, "a"), (2, "b"), (3, "c")]

See also

slice::sort_by in the standard library ↗