Skip to content

Collections

What the standard library packs in your lunchbox

Rust’s standard library ships a handful of collection types. You’ll spend most of your career using three: Vec, HashMap, and HashSet. The rest are there when you need them.

TypeWhat it isWhen to reach for it
Vec<T>growable array, contiguous in memorythe default list
VecDeque<T>double-ended queuepush/pop from both ends
LinkedList<T>doubly-linked listalmost never
HashMap<K, V>unordered key→value mapthe default map
BTreeMap<K, V>ordered key→value mapwhen you want sorted iteration
HashSet<T>unordered unique-value setthe default set
BTreeSet<T>ordered unique-value setwhen you want sorted iteration
BinaryHeap<T>priority queue (max-heap by default)top-k stuff, schedulers

LinkedList exists for completeness but you should almost never use it. Modern CPUs hate pointer chasing; a Vec will beat a linked list at almost every game you can think of.

Vec — the workhorse

You’ve met Vec already in Starting Out. Let’s get cosier:

🦀 main.rs

v.len() is the number of items currently stored. v.capacity() is how many the vec could hold before it has to reallocate. When you push past capacity, Vec allocates a larger buffer (typically doubling) and copies the old contents over. This is amortized O(1) push — the same trick std::vector and Python lists pull.

Iterating

🦀 main.rs

That trio (&xs, &mut xs, xs) is the same trio you see everywhere in Rust. Once it clicks, the same instincts work for every type that owns a heap allocation.

Sorting, searching, and friends

🦀 main.rs

HashMap — keys to values

A HashMap is a hash table. Average O(1) insertion, lookup, and removal.

🦀 main.rs

The entry API — the secret weapon

How many times have you written “increment this counter, or initialise it to 1 if it’s not there”? In Python it’s a defaultdict. In Rust it’s entry:

🦀 main.rs

counts.entry(word) gives you an Entry — basically “a handle to the slot where this key would live.” You can then call:

  • .or_insert(default) — fills it with default if missing, returns &mut V
  • .or_insert_with(|| expensive_default()) — same, but lazy
  • .and_modify(|v| *v += 1) — runs the closure if present

One lookup, one mutation, no double-hashing.

What can be a key?

A type can be a HashMap key if it implements Eq + Hash. That covers strings, integers, tuples of hashable things, and anything you’ve derived Eq + Hash on. Floats can’t (because NaN != NaN). That’s a feature, not a bug.

HashSet — just the keys, please

HashSet<T> is HashMap<T, ()> with nicer methods. You use it when you care whether you’ve seen something, not what it maps to.

🦀 main.rs

BTreeMap and BTreeSet — when order matters

The BTree* variants are sorted by key. They’re O(log n) instead of O(1), but iteration comes out in sorted order without you having to ask. Same API as their Hash* cousins.

🦀 main.rs

A worked example — word histogram

Putting iterators and collections together:

🦀 main.rs

That then_with is a nice little trick — sort primarily by count descending, then by word ascending as a tiebreaker. Makes the output deterministic when two words appear the same number of times.

Next chapter: lifetimes. Where we make the borrow checker into a friend instead of a coworker.