ZetCode

Rust sorting

last modified November 13, 2026

In this article we show how to sort data in Rust.

Sorting methods are defined on slices, so they work on vectors, arrays, and parts of them. All of them sort in place: they rearrange the elements and return nothing. There are two families:

Sorting relies on the Ord trait, a total order in which any two values can be compared. The cmp method returns an Ordering: Less, Equal, or Greater. The examples were tested with Rust 1.97 and the 2024 edition. They are run as Cargo projects with cargo run -q.

Sorting in ascending order

The sort method sorts elements in their natural ascending order.

main.rs
fn main() {
    let mut nums = vec![5, -2, 9, 0, 3, 3, -7];
    nums.sort();
    println!("{nums:?}");

    nums.reverse();
    println!("{nums:?}");

    let mut words = vec!["pear", "Apple", "banana", "apple", "Zebra", "éclair"];
    words.sort();
    println!("{words:?}");

    let mut arr = [3, 1, 2];
    arr.sort();
    println!("{arr:?}");

    let mut letters: Vec<char> = "rustacean".chars().collect();
    letters.sort();
    let s: String = letters.into_iter().collect();
    println!("{s}");
}

Integers are sorted numerically, and reverse turns the result into descending order.

let mut words = vec!["pear", "Apple", "banana", "apple", "Zebra", "éclair"];
words.sort();

Strings are compared byte by byte. In UTF-8, all uppercase ASCII letters come before lowercase ones, and accented letters come after them both. This is not a dictionary order; we fix it later with a key function.

let mut letters: Vec<char> = "rustacean".chars().collect();
letters.sort();

To sort the characters of a string, we collect them into a vector, sort it, and collect them back. Sorted letters are a common way to detect anagrams.

$ cargo run -q
[-7, -2, 0, 3, 3, 5, 9]
[9, 5, 3, 3, 0, -2, -7]
["Apple", "Zebra", "apple", "banana", "pear", "éclair"]
[1, 2, 3]
aacenrstu

Custom comparisons with sort_by

The sort_by method takes a closure that compares two elements and returns an Ordering.

main.rs
use std::cmp::Ordering;

fn main() {
    let mut nums: Vec<i32> = vec![5, -2, 9, 0, 3, -3, -7];

    nums.sort_by(|a, b| b.cmp(a));
    println!("descending: {nums:?}");

    nums.sort_by(|a, b| a.abs().cmp(&b.abs()).then(a.cmp(b)));
    println!("by absolute value: {nums:?}");

    nums.sort_by(|a, b| match (a % 2 == 0, b % 2 == 0) {
        (true, false) => Ordering::Less,
        (false, true) => Ordering::Greater,
        _ => a.cmp(b),
    });
    println!("even first: {nums:?}");

    println!("{:?} {:?} {:?}", 1.cmp(&2), 2.cmp(&2), "b".cmp("a"));
}

Swapping the arguments of cmp reverses the order.

nums.sort_by(|a, b| b.cmp(a));

This sorts in descending order in a single step.

nums.sort_by(|a, b| a.abs().cmp(&b.abs()).then(a.cmp(b)));

We can compare derived values. Here the numbers are ordered by their absolute value; when two absolute values are equal, as for 3 and -3, the then method breaks the tie with a normal comparison. The vector needs an explicit Vec<i32> type: the abs method cannot be called on an integer of a not yet inferred type.

nums.sort_by(|a, b| match (a % 2 == 0, b % 2 == 0) {
    (true, false) => Ordering::Less,
    (false, true) => Ordering::Greater,
    _ => a.cmp(b),
});

A comparator can return an Ordering built by hand. Even numbers come before odd ones; numbers of the same parity are compared normally.

$ cargo run -q
descending: [9, 5, 3, 0, -2, -3, -7]
by absolute value: [0, -2, -3, 3, 5, -7, 9]
even first: [-2, 0, -7, -3, 3, 5, 9]
Less Equal Greater

Sorting by a key

Often it is simpler to say what to sort by. The sort_by_key method calls a closure that extracts a key from each element.

main.rs
use std::cmp::Reverse;

fn main() {
    let mut words = vec!["banana", "fig", "cherry", "kiwi", "apple", "plum"];

    words.sort_by_key(|w| w.len());
    println!("{words:?}");

    words.sort_by_key(|w| Reverse(w.len()));
    println!("{words:?}");

    let mut names = vec!["peter", "Anna", "boris", "Cyril", "anna"];
    names.sort();
    println!("{names:?}");
    names.sort_by_key(|n| n.to_lowercase());
    println!("{names:?}");
}

We sort fruit names by their length.

words.sort_by_key(|w| w.len());
words.sort_by_key(|w| Reverse(w.len()));

The std::cmp::Reverse wrapper reverses the order of a key. Words of equal length keep their previous relative order because the sort is stable.

names.sort_by_key(|n| n.to_lowercase());

Comparing lowercase versions of the strings gives a case-insensitive order. Anna stays before anna because they have the same key and Anna came first.

$ cargo run -q
["fig", "kiwi", "plum", "apple", "banana", "cherry"]
["banana", "cherry", "apple", "kiwi", "plum", "fig"]
["Anna", "Cyril", "anna", "boris", "peter"]
["Anna", "anna", "boris", "Cyril", "peter"]

Caching expensive keys

The sort_by_key method calls the key function on every comparison. When the key is expensive to compute, sort_by_cached_key computes it once per element.

main.rs
use std::cell::Cell;

fn main() {
    let words: Vec<String> = ["Delta", "alpha", "Echo", "charlie", "Bravo", "foxtrot", "golf", "Hotel"]
        .iter()
        .map(|s| s.to_string())
        .collect();

    let calls = Cell::new(0);
    let key = |s: &String| {
        calls.set(calls.get() + 1);
        s.to_lowercase()
    };

    let mut a = words.clone();
    a.sort_by_key(key);
    println!("sort_by_key:        {} key calls", calls.get());

    calls.set(0);
    let mut b = words.clone();
    b.sort_by_cached_key(key);
    println!("sort_by_cached_key: {} key calls", calls.get());

    println!("{b:?} {}", a == b);
}

The key function counts its calls in a Cell.

let key = |s: &String| {
    calls.set(calls.get() + 1);
    s.to_lowercase()
};

The to_lowercase method allocates a new String each time it is called.

a.sort_by_key(key);
b.sort_by_cached_key(key);

For eight strings, sort_by_key computed 24 keys, two for each comparison. The cached version computed exactly eight and then sorted the precomputed keys. The difference grows with the size of the input. For cheap keys such as a field or a length, plain sort_by_key is faster.

$ cargo run -q
sort_by_key:        24 key calls
sort_by_cached_key: 8 key calls
["alpha", "Bravo", "charlie", "Delta", "Echo", "foxtrot", "golf", "Hotel"] true

Floats cannot be sorted with sort

Floating-point numbers implement only PartialOrd, not Ord.

main.rs
fn main() {
    let mut temps = vec![21.5, 19.0, 23.25, 18.5];
    temps.sort();
    println!("{temps:?}");
}

The reason is the NaN value, which is neither smaller, equal to, nor greater than any number, including itself. Floats therefore have no total order.

temps.sort();

The sort method requires T: Ord, so the call does not compile.

$ cargo run -q
error[E0277]: the trait bound `{float}: std::cmp::Ord` is not satisfied
   --> src/main.rs:3:11
    |
  3 |     temps.sort();
    |           ^^^^ the trait `std::cmp::Ord` is not implemented for `{float}`
    |
    = help: the following other types implement trait `std::cmp::Ord`:
              i128
              i16
              i32
              i64
              i8
              isize
              u128
              u16
            and 4 others
note: required by a bound in `std::slice::<impl [T]>::sort`
   --> /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/slice.rs:133:12
    |
131 |     pub fn sort(&mut self)
    |            ---- required by a bound in this associated function
132 |     where
133 |         T: Ord,
    |            ^^^ required by this bound in `std::slice::<impl [T]>::sort`

For more information about this error, try `rustc --explain E0277`.

Sorting floats

The total_cmp method defines a total order for floats, including NaN, and is the right way to sort them.

main.rs
fn main() {
    let mut temps = vec![21.5, 19.0, -0.0, 23.25, 0.0, 18.5, f64::NAN, -3.0, f64::INFINITY];

    temps.sort_by(|a, b| a.total_cmp(b));
    println!("{temps:?}");

    temps.sort_by(|a, b| b.total_cmp(a));
    println!("{temps:?}");

    let mut clean: Vec<f64> = temps.iter().copied().filter(|t| !t.is_nan()).collect();
    clean.sort_by(|a, b| a.partial_cmp(b).unwrap());
    println!("{clean:?}");

    let max = clean.iter().copied().fold(f64::NEG_INFINITY, f64::max);
    println!("max {max}");
}

The data contains a NaN, an infinity, and both zeros.

temps.sort_by(|a, b| a.total_cmp(b));

The total_cmp method follows the IEEE 754 total ordering: negative zero comes before positive zero, and a positive NaN is greater than infinity. Swapping the arguments sorts in descending order.

let mut clean: Vec<f64> = temps.iter().copied().filter(|t| !t.is_nan()).collect();
clean.sort_by(|a, b| a.partial_cmp(b).unwrap());

When the NaNs are removed first, partial_cmp always returns Some and can be unwrapped. It treats 0.0 and -0.0 as equal, so they stay in their previous order.

$ cargo run -q
[-3.0, -0.0, 0.0, 18.5, 19.0, 21.5, 23.25, inf, NaN]
[NaN, inf, 23.25, 21.5, 19.0, 18.5, 0.0, -0.0, -3.0]
[-3.0, 0.0, -0.0, 18.5, 19.0, 21.5, 23.25, inf]
max inf

partial_cmp with NaN

Unwrapping partial_cmp on data that contains a NaN panics.

main.rs
fn main() {
    let mut data = vec![2.5, f64::NAN, 1.0];
    data.sort_by(|a, b| a.partial_cmp(b).unwrap());
    println!("{data:?}");
}

Comparing NaN with any number returns None.

data.sort_by(|a, b| a.partial_cmp(b).unwrap());

The unwrap call panics as soon as the sort compares the NaN. Data from files and sensors can contain NaN values, so total_cmp is the safer choice. The thread ID in the panic message changes from run to run.

$ cargo run -q

thread 'main' (138215) panicked at src/main.rs:3:42:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
$ echo $?
101

Stable and unstable sorting

A stable sort keeps the original order of elements that compare equal.

main.rs
fn main() {
    let orders = [
        ("anna", "books"),
        ("boris", "music"),
        ("cyril", "books"),
        ("dana", "garden"),
        ("emil", "music"),
        ("fero", "books"),
    ];

    let mut stable = orders.to_vec();
    stable.sort_by_key(|o| o.1);
    println!("stable:");
    for (name, cat) in &stable {
        println!("  {cat:<7} {name}");
    }

    let mut unstable = orders.to_vec();
    unstable.sort_unstable_by_key(|o| o.1);
    let cats: Vec<_> = unstable.iter().map(|o| o.1).collect();
    println!("unstable keys: {cats:?}");
}

The orders are listed by customer name and we sort them by category.

stable.sort_by_key(|o| o.1);

Within each category the names remain alphabetical, because that was their order before sorting. This lets us sort by several keys in succession: first by the secondary key, then by the primary one.

unstable.sort_unstable_by_key(|o| o.1);

The unstable sort produces the same sequence of keys, but gives no guarantee about the order of names within a category. Use it when equal elements are indistinguishable, for example plain numbers.

$ cargo run -q
stable:
  books   anna
  books   cyril
  books   fero
  garden  dana
  music   boris
  music   emil
unstable keys: ["books", "books", "books", "garden", "music", "music"]

Sorting by multiple keys

A comparison by several fields can be built by chaining Ordering values or by using a tuple as the key.

main.rs
use std::cmp::Reverse;

#[derive(Debug)]
struct Player {
    name: &'static str,
    team: &'static str,
    score: u32,
}

fn main() {
    let mut players = vec![
        Player { name: "Lucia", team: "red", score: 42 },
        Player { name: "Adam", team: "blue", score: 37 },
        Player { name: "Marek", team: "red", score: 51 },
        Player { name: "Jana", team: "blue", score: 51 },
        Player { name: "Ivan", team: "red", score: 37 },
    ];

    players.sort_by(|a, b| {
        a.team
            .cmp(b.team)
            .then_with(|| b.score.cmp(&a.score))
            .then_with(|| a.name.cmp(b.name))
    });
    for p in &players {
        println!("{:<5} {:>3} {}", p.team, p.score, p.name);
    }
    println!();

    players.sort_by_key(|p| (Reverse(p.score), p.name));
    for p in &players {
        println!("{:>3} {:<6} {}", p.score, p.name, p.team);
    }
}

We sort players by team, then by score in descending order, then by name.

a.team
    .cmp(b.team)
    .then_with(|| b.score.cmp(&a.score))
    .then_with(|| a.name.cmp(b.name))

The then_with method is used only if the previous comparison returned Equal. The closure is evaluated lazily, so the later comparisons are skipped when the team already decides.

players.sort_by_key(|p| (Reverse(p.score), p.name));

Tuples compare element by element, so a tuple key expresses the same idea more briefly. Jana and Marek both have 51 points and are ordered by name.

$ cargo run -q
blue   51 Jana
blue   37 Adam
red    51 Marek
red    42 Lucia
red    37 Ivan

 51 Jana   blue
 51 Marek  red
 42 Lucia  red
 37 Adam   blue
 37 Ivan   red

Deriving Ord

A struct or enum can derive Ord, which makes plain sort work on it.

main.rs
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
enum Priority {
    Critical,
    High,
    Normal,
    Low,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Ticket {
    priority: Priority,
    id: u32,
    title: &'static str,
}

fn main() {
    let mut tickets = vec![
        Ticket { priority: Priority::Low, id: 4, title: "typo in footer" },
        Ticket { priority: Priority::Critical, id: 7, title: "site down" },
        Ticket { priority: Priority::Normal, id: 2, title: "slow search" },
        Ticket { priority: Priority::Critical, id: 3, title: "data loss" },
        Ticket { priority: Priority::High, id: 5, title: "login fails" },
    ];

    tickets.sort();
    for t in &tickets {
        println!("{:<9} #{} {}", format!("{:?}", t.priority), t.id, t.title);
    }

    println!("{}", Priority::High < Priority::Low);
    println!("{:?}", tickets.iter().map(|t| t.priority).max());
}

The derived ordering of an enum follows the order of the variants in the declaration.

enum Priority {
    Critical,
    High,
    Normal,
    Low,
}

Critical is the smallest value, so critical tickets are sorted first. Consequently max returns Low.

struct Ticket {
    priority: Priority,
    id: u32,
    title: &'static str,
}

For structs, the derived ordering compares the fields from top to bottom. Tickets are sorted by priority first and by ID second. Reordering the fields changes the sort order.

$ cargo run -q
Critical  #3 data loss
Critical  #7 site down
High      #5 login fails
Normal    #2 slow search
Low       #4 typo in footer
true
Some(Low)

Implementing Ord

When the derived ordering is not suitable, we implement Ord by hand.

main.rs
use std::cmp::Ordering;

#[derive(Debug, PartialEq, Eq)]
struct Version {
    text: String,
}

impl Version {
    fn parts(&self) -> Vec<u32> {
        self.text.split('.').map(|p| p.parse().unwrap_or(0)).collect()
    }
}

impl Ord for Version {
    fn cmp(&self, other: &Self) -> Ordering {
        self.parts().cmp(&other.parts())
    }
}

impl PartialOrd for Version {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

fn main() {
    let input = ["1.10.0", "1.2.3", "1.9", "2.0.1", "1.10", "0.9.12"];

    let mut as_text = input.to_vec();
    as_text.sort();
    println!("{as_text:?}");

    let mut versions: Vec<Version> = input.iter().map(|s| Version { text: s.to_string() }).collect();
    versions.sort();
    let sorted: Vec<&str> = versions.iter().map(|v| v.text.as_str()).collect();
    println!("{sorted:?}");
}

Version strings sorted as text put 1.10 before 1.2.3, because the character 1 is smaller than 2.

impl Ord for Version {
    fn cmp(&self, other: &Self) -> Ordering {
        self.parts().cmp(&other.parts())
    }
}

We split each version into numbers and compare the vectors. Vectors are compared element by element, and a shorter vector that is a prefix of a longer one is smaller, so 1.10 comes before 1.10.0.

impl PartialOrd for Version {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

Ord requires PartialOrd, Eq, and PartialEq. The PartialOrd implementation must agree with Ord, so it simply delegates to it. Note that the derived PartialEq compares the text, so 1.10 and 1.10.0 are not equal even though they are ordered as neighbours; for a strict implementation, equality should be based on the same parts.

$ cargo run -q
["0.9.12", "1.10", "1.10.0", "1.2.3", "1.9", "2.0.1"]
["0.9.12", "1.2.3", "1.9", "1.10", "1.10.0", "2.0.1"]

Natural sort order

File names with numbers sort badly as plain strings. A key that separates the text from the number gives the natural order.

main.rs
fn split_num(s: &str) -> (&str, u64) {
    let pos = s.find(|c: char| c.is_ascii_digit()).unwrap_or(s.len());
    let (head, tail) = s.split_at(pos);
    let num = tail.trim_end_matches(".txt").parse().unwrap_or(0);
    (head, num)
}

fn main() {
    let mut files = vec!["file10.txt", "file2.txt", "file1.txt", "doc3.txt", "file20.txt", "doc12.txt"];

    files.sort();
    println!("{files:?}");

    files.sort_by_key(|f| split_num(f));
    println!("{files:?}");
}

The split_num function splits a name such as file10.txt into ("file", 10).

let pos = s.find(|c: char| c.is_ascii_digit()).unwrap_or(s.len());
let (head, tail) = s.split_at(pos);

We find the first digit and split the string there. The number is parsed from the rest without the extension.

files.sort_by_key(|f| split_num(f));

The tuple key compares the prefixes as text and the numbers as numbers, so file2 now comes before file10. The key borrows from the element, which works here because the elements are &str references that outlive the sort.

$ cargo run -q
["doc12.txt", "doc3.txt", "file1.txt", "file10.txt", "file2.txt", "file20.txt"]
["doc3.txt", "doc12.txt", "file1.txt", "file2.txt", "file10.txt", "file20.txt"]

Borrowed keys

A key that borrows from the element itself cannot be returned from the sort_by_key closure.

main.rs
struct City {
    name: String,
    population: u32,
}

fn main() {
    let mut cities = vec![
        City { name: String::from("Košice"), population: 228_000 },
        City { name: String::from("Bratislava"), population: 475_000 },
    ];

    cities.sort_by_key(|c| &c.name);

    for c in &cities {
        println!("{} {}", c.name, c.population);
    }
}

We try to sort cities by name without cloning the string.

cities.sort_by_key(|c| &c.name);

The signature of sort_by_key requires the key type to be independent of the borrow of the element, but &c.name lives only as long as that borrow. The compiler rejects the closure.

$ cargo run -q
error: lifetime may not live long enough
  --> src/main.rs:12:28
   |
12 |     cities.sort_by_key(|c| &c.name);
   |                         -- ^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                         ||
   |                         |return type of closure is &'2 std::string::String
   |                         has type `&'1 City`

Sorting with borrowed fields

The fix is sort_by, which compares two references directly.

main.rs
struct City {
    name: String,
    population: u32,
}

fn main() {
    let mut cities = vec![
        City { name: String::from("Košice"), population: 228_000 },
        City { name: String::from("Bratislava"), population: 475_000 },
        City { name: String::from("Žilina"), population: 80_000 },
        City { name: String::from("Banská Bystrica"), population: 74_000 },
    ];

    cities.sort_by(|a, b| a.name.cmp(&b.name));
    for c in &cities {
        println!("{:<16} {:>7}", c.name, c.population);
    }
    println!();

    cities.sort_by_key(|c| c.population);
    for c in &cities {
        println!("{:<16} {:>7}", c.name, c.population);
    }
}

Inside the comparator, both names are borrowed only for the comparison.

cities.sort_by(|a, b| a.name.cmp(&b.name));

No string is cloned. Another option is sort_by_key(|c| c.name.clone()), which allocates on every comparison.

cities.sort_by_key(|c| c.population);

Copy values such as numbers can be returned from the key function without any problem. Note that the names are sorted by bytes: Ž comes after the ASCII letters. Locale-aware collation needs a crate such as icu_collator.

$ cargo run -q
Banská Bystrica    74000
Bratislava        475000
Košice            228000
Žilina             80000

Banská Bystrica    74000
Žilina             80000
Košice            228000
Bratislava        475000

Partial sorting and selection

Sometimes we need to sort only part of the data, or we only need the element that would be at a certain position.

main.rs
fn main() {
    let mut v = vec![9, 4, 7, 1, 8, 2, 6, 3, 5];

    v[3..].sort();
    println!("tail sorted:  {v:?}");

    let mut data: Vec<u32> = (1..=25).map(|i| i * 37 % 101).collect();
    println!("data:         {data:?}");
    let mid = data.len() / 2;
    let (_, median, _) = data.select_nth_unstable(mid);
    println!("median: {median}");
    println!("after select: {data:?}");

    let mut scores = [55, 91, 78, 64, 99, 70, 85, 42];
    let k = 3;
    scores.select_nth_unstable_by(k - 1, |a, b| b.cmp(a));
    let mut top = scores[..k].to_vec();
    top.sort_unstable_by(|a, b| b.cmp(a));
    println!("top {k}: {top:?}");

    println!("{} {}", v.is_sorted(), top.is_sorted_by(|a, b| a >= b));
}

Since sorting works on slices, we can sort a range of a vector.

v[3..].sort();

Only the elements from index 3 on are sorted; the first three stay where they are.

let (_, median, _) = data.select_nth_unstable(mid);

The select_nth_unstable method moves the element that belongs at index mid to that place. All smaller elements end up before it and all larger after it, but neither side is sorted. This takes linear time on average, faster than a full sort. It returns the left part, the element, and the right part.

scores.select_nth_unstable_by(k - 1, |a, b| b.cmp(a));
let mut top = scores[..k].to_vec();

With a reversed comparator, the first k elements are the k largest. We sort just these three.

println!("{} {}", v.is_sorted(), top.is_sorted_by(|a, b| a >= b));

The is_sorted and is_sorted_by methods check the order without changing anything.

$ cargo run -q
tail sorted:  [9, 4, 7, 1, 2, 3, 5, 6, 8]
data:         [37, 74, 10, 47, 84, 20, 57, 94, 30, 67, 3, 40, 77, 13, 50, 87, 23, 60, 97, 33, 70, 6, 43, 80, 16]
median: 47
after select: [16, 10, 20, 30, 3, 13, 23, 33, 6, 37, 40, 43, 47, 50, 57, 60, 67, 70, 74, 77, 80, 84, 87, 94, 97]
top 3: [99, 91, 85]
false true

Sorting, deduplicating, and searching

A sorted vector supports removing duplicates and fast searching.

main.rs
fn main() {
    let mut tags = vec!["rust", "web", "cli", "rust", "async", "web", "rust"];

    tags.sort_unstable();
    println!("{tags:?}");

    tags.dedup();
    println!("{tags:?}");

    match tags.binary_search(&"db") {
        Ok(i) => println!("found at {i}"),
        Err(i) => {
            tags.insert(i, "db");
            println!("inserted at {i}: {tags:?}");
        }
    }
}

The dedup method removes consecutive equal elements. After sorting, all equal elements are adjacent, so every duplicate is removed.

match tags.binary_search(&"db") {
    Ok(i) => println!("found at {i}"),
    Err(i) => {
        tags.insert(i, "db");

The binary_search method needs sorted input. If the element is missing, the Err variant holds the index where it can be inserted so that the vector stays sorted.

$ cargo run -q
["async", "cli", "rust", "rust", "rust", "web", "web"]
["async", "cli", "rust", "web"]
inserted at 2: ["async", "cli", "db", "rust", "web"]

Performance

The last example compares stable and unstable sorting on ten million numbers.

main.rs
use std::time::Instant;

fn main() {
    let n = 10_000_000u64;
    let data: Vec<u64> = (0..n).map(|i| i.wrapping_mul(0x9E37_79B9_7F4A_7C15) % 1000).collect();

    let mut a = data.clone();
    let t = Instant::now();
    a.sort();
    println!("sort:          {:?}", t.elapsed());

    let mut b = data.clone();
    let t = Instant::now();
    b.sort_unstable();
    println!("sort_unstable: {:?}", t.elapsed());

    let t = Instant::now();
    a.sort();
    println!("sort (sorted): {:?}", t.elapsed());

    println!("equal: {}", a == b);
}

The numbers are pseudo-random values between 0 and 999, so there are many duplicates.

a.sort();
b.sort_unstable();

The stable sort is a merge sort variant (driftsort) that needs a buffer of up to half the input. The unstable sort is a quicksort variant (ipnsort) that works in place.

let t = Instant::now();
a.sort();

Both algorithms detect already sorted runs, so sorting sorted data is very fast.

On an Intel Core i5-1135G7 in release mode, the unstable sort was more than twice as fast. The timings vary between runs.

$ cargo run -q --release
sort:          173.534614ms
sort_unstable: 78.249409ms
sort (sorted): 6.368766ms
equal: true

Source

slice::sort documentation

std::cmp::Ord documentation

f64::total_cmp documentation

In this article we have sorted data in Rust. Ordered collections are covered in Rust HashSet and BTreeMap, and more slice methods in Rust vectors and Rust slices.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Rust tutorials.