How to sort a Vec

You can sort a `Vec` in place using the `.sort()` method for simple types or `.sort_by_key()` for custom ordering, while `.sorted()` from the `itertools` crate or `.into_iter().sorted()` provides a non-destructive alternative.

When the data arrives in chaos

You just pulled a list of scores from a database, and they arrived in the order the database felt like spitting them out. You need the top ten at the front. Or maybe you're processing sensor readings and need them chronologically. You have a Vec full of data, and it's currently a jumble. You need to put it in order.

Rust gives you several ways to sort, but they all share a philosophy: be explicit about what you're doing, and prefer efficiency over convenience. The default approach mutates the vector in place. There is no hidden allocation. The compiler forces you to acknowledge that the data is changing.

Sorting in place saves memory

Rust prefers sorting in place. Think of a deck of cards. You can sort the deck by rearranging the cards in your hand, or you can deal a brand new deck onto the table in the right order and throw the old one away. Rearranging in your hand is faster and uses less memory. Rust's .sort() works like that. It shuffles the elements inside the existing vector. It doesn't create a new vector.

This means you need a mut vector. If you try to sort an immutable vector, the compiler stops you. The error is E0596, complaining that you cannot borrow as mutable. The fix is always the same: add mut to the binding.

fn main() {
    // We need mut because sort rearranges elements in place.
    // Without mut, the compiler rejects this with E0596.
    let mut numbers = vec![3, 1, 4, 1, 5];

    // .sort() uses TimSort, a stable, adaptive algorithm.
    // It's O(n log n) worst case and fast on partially sorted data.
    numbers.sort();

    println!("{:?}", numbers);
    // Output: [1, 1, 3, 4, 5]
}

Make your vector mutable before you sort. The compiler won't guess you want to mutate it.

The compiler demands total order

When you call .sort(), the compiler checks that your elements implement the Ord trait. Ord is the trait that defines total ordering. It means for any two values a and b, you can always say whether a < b, a == b, or a > b. If your type doesn't implement Ord, you get a compiler error.

The error usually looks like E0277, complaining that the trait bound T: Ord is not satisfied. This happens most often with floats. f32 and f64 implement PartialOrd, not Ord. The reason is NaN. A total order requires transitivity and no gaps. NaN breaks this. You can't say NaN < NaN is true or false consistently. If you try vec![1.0, 2.0].sort(), the compiler rejects it.

You have to use .sort_by() or .sort_by_key() to handle floats. You need to provide a comparison function that decides what to do with NaN.

fn main() {
    let mut floats = vec![3.14, 1.59, 2.65];

    // floats.sort(); // Error E0277: f64 does not implement Ord

    // Use sort_by to define how to compare floats.
    // partial_cmp returns Option<Ordering>, so we unwrap here.
    // This panics if NaN is present. Handle NaN explicitly in real code.
    floats.sort_by(|a, b| a.partial_cmp(b).unwrap());

    println!("{:?}", floats);
    // Output: [1.59, 2.65, 3.14]
}

Floats don't sort by default. Handle the NaN case or use sort_by.

Sorting structs and custom logic

Real code rarely sorts integers. You usually sort structs by a field. The standard tool is .sort_by_key(). This method extracts a key from each element, sorts by those keys, and leaves the elements in the new order.

There is a performance detail here that matters. sort_by_key extracts the key once per element. It pairs the key with the element, sorts the pairs, and then drops the keys. This is the Schwartzian transform pattern. If extracting the key is expensive, sort_by_key saves you from computing it dozens of times during the sort.

#[derive(Debug)]
struct Task {
    name: String,
    priority: u8,
    due_date: u32,
}

/// Sorts tasks by priority, lowest number first.
fn sort_by_priority(tasks: &mut Vec<Task>) {
    // sort_by_key extracts priority once per task.
    // This is faster than sort_by if priority extraction were complex.
    tasks.sort_by_key(|t| t.priority);
}

fn main() {
    let mut tasks = vec![
        Task { name: "Fix bug".to_string(), priority: 1, due_date: 10 },
        Task { name: "Refactor".to_string(), priority: 3, due_date: 5 },
        Task { name: "Write docs".to_string(), priority: 2, due_date: 8 },
    ];

    sort_by_priority(&mut tasks);

    println!("{:?}", tasks);
    // Output: [Fix bug, Write docs, Refactor]
}

Community convention: use sort_by_key whenever you can extract a simple key. It's the performance trap that bites beginners. sort_by calls your closure many times. If your closure does a heavy calculation, sort_by will be painfully slow. sort_by_key computes the value once.

Reach for sort_by_key first. It's the performance trap that bites beginners.

Stability matters when you sort twice

Rust's .sort() is stable. A stable sort preserves the relative order of elements that compare as equal. This matters when you chain sorts.

Imagine you have tasks sorted by due date. You want to re-sort by priority, but you want tasks with the same priority to stay in due-date order. A stable sort keeps the due-date order intact within each priority group. An unstable sort might scramble them.

#[derive(Debug)]
struct Item {
    name: String,
    group: u8,
    id: u32,
}

fn main() {
    let mut items = vec![
        Item { name: "A".to_string(), group: 1, id: 10 },
        Item { name: "B".to_string(), group: 1, id: 5 },
        Item { name: "C".to_string(), group: 2, id: 2 },
    ];

    // First sort by id.
    items.sort_by_key(|i| i.id);
    // Order: C (id 2), B (id 5), A (id 10)

    // Now sort by group.
    // Stable sort keeps B before A because they have the same group
    // and B appeared before A in the previous order.
    items.sort_by_key(|i| i.group);

    println!("{:?}", items);
    // Output: [B, A, C]
    // B and A are group 1. B has id 5, A has id 10.
    // Stability preserved the id order.
}

Convention aside: if you just want descending order, the community often prefers items.sort(); items.reverse(); over items.sort_by(|a, b| b.cmp(a));. The reverse is clearer and avoids the closure overhead.

Stability preserves your previous work. Trust the stable sort when chaining operations.

Unstable sort for raw speed

If stability doesn't matter, you can use .sort_unstable(). This method uses Quicksort instead of TimSort. Quicksort can be significantly faster on random data, especially for primitive types like u32 or i32. It doesn't guarantee that equal elements keep their relative order.

Use this only when you have measured a bottleneck and stability is irrelevant. For most application code, the difference is negligible, and stability is a safety net you don't want to lose.

fn main() {
    let mut data = vec![4, 1, 3, 2, 5];

    // sort_unstable uses Quicksort.
    // Faster on random primitives, but order of equal elements is undefined.
    data.sort_unstable();

    println!("{:?}", data);
    // Output: [1, 2, 3, 4, 5]
}

Use unstable only when you measured the cost and stability is irrelevant.

Non-mutating sorts

Sometimes you need to keep the original vector unchanged. Rust doesn't provide a .sorted() method on Vec in the standard library. You have two choices: clone the vector and sort the clone, or use an iterator approach.

Cloning is explicit. You pay the allocation cost, you get a new vector, and you sort it. This is the standard library way.

fn main() {
    let numbers = vec![3, 1, 4, 1, 5];

    // Clone creates a new Vec. We sort the clone.
    // Original remains untouched.
    let mut sorted = numbers.clone();
    sorted.sort();

    println!("Original: {:?}", numbers);
    println!("Sorted:   {:?}", sorted);
}

If you are already working with iterators, or you want a functional style, the itertools crate provides a .sorted() method. This collects the iterator into a vector and sorts it. It's ergonomic for pipelines.

use itertools::Itertools;

fn main() {
    let numbers = vec![3, 1, 4, 1, 5];

    // itertools::sorted collects and sorts in one step.
    // Returns a new Vec. Original is unchanged.
    let sorted = numbers.iter().copied().sorted().collect::<Vec<_>>();

    println!("{:?}", sorted);
    // Output: [1, 1, 3, 4, 5]
}

Clone if you must keep the original. The cost is real, but the intent is clear.

Decision matrix

Use .sort() when your elements implement Ord and you want the standard stable sort. Use .sort_by_key() when you need to sort by a specific field or computed value; it extracts the key once per element, which is faster than repeated comparisons. Use .sort_by() when you need custom comparison logic that doesn't fit a simple key extraction, like handling NaN in floats or complex multi-field logic where keys are expensive to store. Use .sort_unstable() when stability doesn't matter and you want the absolute fastest sort for primitive types; it uses Quicksort and can be significantly faster than TimSort on random data. Use itertools::sorted() when you need a non-mutating sort that returns a new vector without manually cloning; this is ergonomic for functional pipelines where you want to keep the original data intact.

Pick the method that matches your data and your constraints. The compiler will force you to be explicit.

Where to go next