Skip to content
Select theme

How to get the largest element from a collection

Short answer: for a one-off, iter().max() — it returns Option<&T>, so an empty collection gives None instead of a panic. When the largest has to come out again and again, use BinaryHeap: push adds to it, pop takes the largest back out, and peek looks without taking.

From a fresh project — cargo new max-demo — replace the contents of src/main.rs with each block below and run it with cargo run. The project name shows up only in cargo’s own output; nothing else here depends on it. Everything here is Rust 1.99.0, edition 2024.

Some(9)
Some(9)
None

max lives on Iterator, so a Vec or a slice reaches it through iter(). The answer is an Option because the collection can be empty, and the standard library’s own words are that “if several elements are equally maximum, the last element is returned”. One pass, no allocation, nothing that can panic.

Both lines print the same — references print transparently in Debug — but the types differ. iter().max() borrows and gives Option<&T>, leaving the collection usable; copied().max() gives an owned Option<T> for Copy types; into_iter().max() consumes the collection to get there.

Some(("Lin", 5))
Some("Grace")
names still usable: ["Ada", "Grace", "Lin"]

max_by_key compares whatever the closure returns, which is how a Vec of structs is searched by a field. Both 5s tied and the later one won, exactly as the documentation says. The second half shows the borrow from the outside: longest references names, and names still prints afterwards — the same rule find lives under.

BinaryHeap when you keep taking the largest

Section titled “BinaryHeap when you keep taking the largest”
peek: Some(9)
after push(11), peek: Some(11)
pop: Some(11)
iter: [9, 4, 7, 3, 1, 2]
drain: 9
drain: 7
drain: 4
drain: 3
drain: 2
drain: 1
pop on empty: None

A BinaryHeap is a priority queue. peek costs O(1) and returns Option<&T>; pop costs O(log n) in the worst case and returns Option<T>; converting a Vec into one happens in place in O(n). The drain loop is the whole pattern — while let Some(n) = heap.pop() runs until the heap is empty, always taking the largest first.

Two things surprise people. iter() walks the underlying array “in arbitrary order”, not sorted — the [9, 4, 7, 3, 1, 2] above — so reach for pop when order matters, or into_sorted_vec() to consume the heap into a sorted Vec. And the heap allocates, which is the wrong price for a single maximum.

One maximum, asked once: iter().max() — one linear pass, no allocation, an Option back. The heap earns its keep when the question repeats: each push and pop is logarithmic where a fresh scan per answer is linear. For the top few rather than the top one, select_nth_unstable partitions around the nth element in O(n) without allocating; sorting everything is its own page.

max needs Ord, and f64 only has PartialOrd, because NaN is incomparable:

error[E0277]: the trait bound `f64: Ord` is not satisfied
--> src/main.rs:3:32
|
3 | let hottest = temps.iter().max();
| ^^^ the trait `Ord` is not implemented for `f64`
|
= help: the following other types implement trait `Ord`:
i128
i16
i32
i64
i8
isize
u128
u16
and 4 others
= note: required for `&f64` to implement `Ord`
note: required by a bound in `std::iter::Iterator::max`
--> /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:3249:21
|
3246 | fn max(self) -> Option<Self::Item>
| --- required by a bound in this associated function
...
3249 | Self::Item: Ord,
| ^^^ required by this bound in `Iterator::max`
For more information about this error, try `rustc --explain E0277`.

The fix is max_by with total_cmp, which orders floats by IEEE 754’s totalOrder:

Some(23.8)

total_cmp gives every float a place in that order, so the usual f64::NAN counts as the largest value instead of quietly poisoning the comparison.

Next in this chapter: why indexing an array out of bounds panics.