What sorting a Vec means
Rust sorts a Vec in place: the methods change the vector they are called on and return nothing (the unit value ()). So the vector must be declared mut, and writing let sorted = v.sort(); gives you (), not a sorted vector.
There are three everyday methods:
vec.sort(); // ascending order (default)
vec.sort_by(|a, b| a.cmp(b)); // with a comparator
vec.sort_by_key(|k| /* key expr */); // sort by a key
They are stable: elements that compare equal keep their original relative order. Each has an unstable twin (sort_unstable, sort_unstable_by, sort_unstable_by_key) that doesn't promise this. For plain integers it makes no visible difference, because equal integers are indistinguishable.
Default order: sort()
sort() uses the type's natural order. It works on any element type that implements Ord, which includes integers, char, &str, String, tuples and vectors of those.
let mut numbers = vec![11, 3, 7, 5, 2];
numbers.sort(); // numbers becomes [2, 3, 5, 7, 11]
Tuples and vectors compare element by element, from the left. That makes sort() enough for a vector of pairs when "first value, then second value" is the order you want:
let mut pairs = vec![vec![1, 5], vec![12, 5], vec![10, 1], vec![1, 3]];
pairs.sort();
// pairs is now [[1, 3], [1, 5], [10, 1], [12, 5]]
Custom order: sort_by with a comparator
sort_by takes a closure that receives two references, a and b, and returns a std::cmp::Ordering: Less, Equal or Greater. If it returns Less, a goes before b; if Greater, b goes before a. The method cmp produces such a value, so the ascending order is |a, b| a.cmp(b). Swapping the operands reverses it:
let mut numbers = vec![11, 3, 7, 5, 2];
numbers.sort_by(|a, b| b.cmp(a));
// numbers is now [11, 7, 5, 3, 2]
The long form of the same closure spells out the types: |a: &i32, b: &i32| -> std::cmp::Ordering { a.cmp(b) }. The short form works because the compiler infers them.
Chaining comparisons
To sort by one field and break ties with another, chain comparisons with then. The second comparison is used only when the first returns Equal:
let mut coordinates = vec![(3, 2), (2, 3), (3, 5)];
coordinates.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
// coordinates is now [(2, 3), (3, 5), (3, 2)]
The first fields ascend (a.0.cmp(&b.0)), and among equal first fields the second ones descend (b.1.cmp(&a.1), operands swapped). Tuple fields are read with .0 and .1, and cmp takes the other value by reference, hence the &.
Sorting by a key: sort_by_key
When the order is "compare these computed values", sort_by_key is shorter. The closure returns a key for each element, and the vector is ordered by the keys:
let mut words = vec!["banana", "pie", "Washington", "book"];
words.sort_by_key(|a| a.len());
// words is now ["pie", "book", "banana", "Washington"]
The vector keeps its original elements, only in the order of their keys. The key type must itself implement Ord. A name sorted ignoring case uses a lowercase copy as the key:
let mut names = vec!["Mircea", "john", "Andy"];
names.sort_by_key(|name| name.to_lowercase());
// names is now ["Andy", "john", "Mircea"]
Without the key, names.sort() compares bytes, and every uppercase letter comes before every lowercase one, giving ["Andy", "Mircea", "john"]. Note that sort_by_key calls the closure on every comparison, so it builds a new lowercase string many times. sort_by_cached_key computes each key once, which helps when keys are expensive to build.
For a descending order by key, wrap the key in std::cmp::Reverse: d.sort_by_key(|&x| std::cmp::Reverse(x)) turns [3, 1, 2] into [3, 2, 1].
Gotchas
- Floats can't use
sort().f64doesn't implementOrd, becauseNaNisn't comparable. Callingsort()on aVec<f64>is a compile error:the trait bound {float}: Ord is not satisfied. Use a comparator:v.sort_by(|a, b| a.partial_cmp(b).unwrap())works when no value isNaN, andtotal_cmphandles every value. - Forgetting
mut. Callingv.sort()on a non-mutbinding fails withcannot borrow `v` as mutable, as it is not declared as mutable. - Using the result. Sorting returns
(), so the sorted vector is the variable you called it on. - A reversed comparator.
a.cmp(b)is ascending andb.cmp(a)is descending. Mixing them up flips the whole order.
Complexity
Sorting a Vec takes O(n log n * C) time, where C is the cost of one comparison. Comparing two integers is O(1), so sorting numbers is O(n log n). Comparing two strings costs O(L), where L is their length, so sorting strings takes O(n log n * L).
The vector is rearranged in place and nothing new is returned, but the stable methods may use a temporary buffer, so their extra space can reach O(n). The sort_unstable family works fully in place and needs only O(1) extra space apart from a small amount of stack for the recursion. That difference matters when a problem insists on O(1) space.
The exercise
The lesson asks for three small sorts. Each is built the same way: a mut variable, then one sorting call.
- Numbers, descending. Declare
numbersasvec![2, 1, 3, 10]and callsort_bywith|a, b| b.cmp(a). The result is[10, 3, 2, 1]. - Coordinates. Declare
coordinatesasvec![(3, 2), (2, 3), (3, 5)]. Sort by the first value ascending and break ties by the second value descending, witha.0.cmp(&b.0).then(b.1.cmp(&a.1)). The result is[(2, 3), (3, 5), (3, 2)]. - Names, ignoring case. Declare
namesasvec!["Mircea", "john", "Andy"], a vector of&str, and callsort_by_keywith|name| name.to_lowercase(). The result is["Andy", "john", "Mircea"].
let mut numbers = vec![2, 1, 3, 10];
numbers.sort_by(|a, b| b.cmp(a));
let mut coordinates = vec![(3, 2), (2, 3), (3, 5)];
coordinates.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
let mut names = vec!["Mircea", "john", "Andy"];
names.sort_by_key(|name| name.to_lowercase());
Key takeaways
sort(),sort_by()andsort_by_key()sort amutvector in place and return().- A comparator returns an
Ordering.a.cmp(b)sorts ascending,b.cmp(a)descending, andthenbreaks ties. sort_by_keyorders by a computed key, andReverseinverts it.- Floats need
sort_bywithpartial_cmportotal_cmp. - Sorting numbers takes
O(n log n)time. Usesort_unstablewhen you need the in-place guarantee.
