Collections in Rust
Work with Vec<T>, HashMap<K,V>, HashSet, BTreeMap, and iterator adapters in Rust.
Vec<T> — Dynamic Array
Vec<T> is Rust’s workhorse collection — a heap-allocated, contiguous, growable array. It is the right default choice whenever you need to store a variable number of values of the same type. Because elements are stored contiguously in memory, iteration and random access are cache-friendly and fast. Pre-allocating with Vec::with_capacity avoids repeated reallocations when the final size is known in advance.
fn main() {
// Create
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3, 4, 5];
let _v3: Vec<i32> = Vec::with_capacity(100); // pre-allocate to avoid reallocations
// Add elements
v.push(10);
v.push(20);
v.push(30);
// Access — indexing panics on out of bounds; .get() returns Option
println!("{}", v[0]); // 10 (panics if out of bounds)
println!("{:?}", v.get(1)); // Some(20) — safe access
println!("{:?}", v.get(99)); // None — no panic
// Modify
v[0] = 100;
// Remove
let last = v.pop(); // Some(30) — removes from the end
v.remove(0); // removes index 0, shifts remaining elements
// Length and capacity
println!("len: {}", v2.len()); // 5
println!("is_empty: {}", v.is_empty());
// Iterate by reference (v2 is not consumed)
for n in &v2 {
print!("{} ", n);
}
println!();
// Slice operations
println!("{:?}", &v2[1..4]); // [2, 3, 4]
println!("{:?}", v2.first()); // Some(1)
println!("{:?}", v2.last()); // Some(5)
}
Common Vec Operations
fn main() {
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6];
// Sort then deduplicate consecutive duplicates
v.sort();
println!("{:?}", v); // [1, 1, 2, 3, 4, 5, 6, 9]
v.dedup(); // dedup only removes consecutive duplicates — sort first
println!("{:?}", v); // [1, 2, 3, 4, 5, 6, 9]
// retain keeps only elements satisfying the predicate
v.retain(|&x| x % 2 == 0);
println!("{:?}", v); // [2, 4, 6]
// Extend appends elements from any iterable
let mut a = vec![1, 2, 3];
a.extend([4, 5, 6]);
println!("{:?}", a); // [1, 2, 3, 4, 5, 6]
// chain + collect concatenates without mutating either source
let b = vec![7, 8, 9];
let c: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
println!("{:?}", c); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// flatten removes one level of nesting from a Vec<Vec<T>>
let nested = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
let flat: Vec<i32> = nested.into_iter().flatten().collect();
println!("{:?}", flat); // [1, 2, 3, 4, 5, 6]
}
HashMap<K, V> — Hash Map
HashMap maps keys to values with O(1) average-case lookup, insert, and remove. It is the right choice when you need to associate data with arbitrary keys and access it by key later. The standard library’s HashMap uses a high-quality hash function by default that is resistant to hash-flooding attacks — if raw speed matters more than security you can swap in a faster hasher.
use std::collections::HashMap;
fn main() {
let mut scores: HashMap<String, i32> = HashMap::new();
// Insert key-value pairs
scores.insert(String::from("Alice"), 95);
scores.insert(String::from("Bob"), 87);
scores.insert(String::from("Charlie"), 92);
// Access — .get() returns Option, indexing panics on missing key
println!("{:?}", scores.get("Alice")); // Some(95)
println!("{:?}", scores.get("Dave")); // None
println!("{}", scores["Alice"]); // 95 (panics if missing)
// Membership test
println!("{}", scores.contains_key("Bob")); // true
// Remove
scores.remove("Bob");
// Iterate — order is not guaranteed
for (name, score) in &scores {
println!("{}: {}", name, score);
}
// Overwrite an existing entry
scores.insert(String::from("Alice"), 100);
println!("{}", scores["Alice"]); // 100
}
The Entry API
The entry API solves a common pattern — insert a value if the key is absent, or update it if present — without a separate lookup. It avoids the double-hash and the clone that a contains_key + insert pair would require, making it both cleaner and faster for accumulation patterns like word counting.
use std::collections::HashMap;
fn word_count(text: &str) -> HashMap<&str, usize> {
let mut map = HashMap::new();
for word in text.split_whitespace() {
// or_insert returns a mutable reference to the value, inserting 0 if absent
*map.entry(word).or_insert(0) += 1;
}
map
}
fn main() {
let counts = word_count("the quick brown fox jumps over the lazy fox");
let mut pairs: Vec<_> = counts.iter().collect();
pairs.sort_by_key(|&(_, v)| std::cmp::Reverse(*v));
for (word, count) in pairs.iter().take(3) {
println!("{}: {}", word, count);
}
}
use std::collections::HashMap;
fn main() {
let mut map: HashMap<&str, Vec<i32>> = HashMap::new();
// or_insert_with creates the Vec lazily only when the key is absent
map.entry("evens").or_insert_with(Vec::new).push(2);
map.entry("evens").or_insert_with(Vec::new).push(4);
map.entry("odds").or_insert_with(Vec::new).push(1);
println!("{:?}", map); // {"evens": [2, 4], "odds": [1]}
}
HashSet<T> — Unique Elements
HashSet is a collection of unique values backed by a hash table. It is ideal when you care about membership rather than order — checking whether an element exists, deduplicating a list, or computing set operations like union and intersection. The underlying hash mechanism is the same as HashMap, so lookup is O(1) average.
use std::collections::HashSet;
fn main() {
let mut set: HashSet<i32> = HashSet::new();
set.insert(1);
set.insert(2);
set.insert(3);
set.insert(2); // duplicate — silently ignored
println!("{}", set.contains(&2)); // true
println!("{}", set.len()); // 3
let a: HashSet<i32> = [1, 2, 3, 4].iter().copied().collect();
let b: HashSet<i32> = [3, 4, 5, 6].iter().copied().collect();
// Set algebra — all return iterators over references
let union: HashSet<_> = a.union(&b).collect();
let intersection: HashSet<_> = a.intersection(&b).collect();
let difference: HashSet<_> = a.difference(&b).collect();
println!("union: {:?}", union);
println!("intersection: {:?}", intersection); // {3, 4}
println!("a - b: {:?}", difference); // {1, 2}
println!("a subset of union: {}", a.is_subset(&union.into_iter().copied().collect()));
}
BTreeMap<K, V> — Sorted Map
BTreeMap is like HashMap but keys are always stored in sorted order, which means iteration visits keys in ascending order and you can efficiently query ranges. The trade-off is O(log n) operations instead of O(1). Use it when you need sorted output, need to find the minimum or maximum key, or need to query a range of keys.
use std::collections::BTreeMap;
fn main() {
let mut scores: BTreeMap<&str, i32> = BTreeMap::new();
scores.insert("Charlie", 92);
scores.insert("Alice", 95);
scores.insert("Bob", 87);
// Iteration always visits keys in sorted (alphabetical) order
for (name, score) in &scores {
println!("{}: {}", name, score);
}
// Alice: 95
// Bob: 87
// Charlie: 92
// Range queries — only possible with BTreeMap, not HashMap
for (name, score) in scores.range("Alice"..="Bob") {
println!("{}: {}", name, score);
}
}
VecDeque<T> — Double-Ended Queue
VecDeque is a ring buffer that supports efficient push and pop from both ends. It is the right choice when you need a queue (FIFO) or deque — pushing to the back and popping from the front is O(1), unlike Vec where removing from the front is O(n) because it shifts all elements.
use std::collections::VecDeque;
fn main() {
let mut deque: VecDeque<i32> = VecDeque::new();
// O(1) push to either end
deque.push_back(1);
deque.push_back(2);
deque.push_front(0);
println!("{:?}", deque); // [0, 1, 2]
// O(1) pop from either end
println!("{:?}", deque.pop_front()); // Some(0)
println!("{:?}", deque.pop_back()); // Some(2)
}
Collecting into Collections
The .collect() method is how you materialize an iterator into a concrete collection. Because collect is generic, Rust can build any collection type as long as you tell it which one — either through a type annotation or the turbofish syntax. This makes iterator pipelines compose naturally with any collection you need.
use std::collections::{HashMap, HashSet};
fn main() {
// Vec — most common collect target
let squares: Vec<i32> = (1..=5).map(|x| x * x).collect();
println!("{:?}", squares); // [1, 4, 9, 16, 25]
// HashSet — automatically deduplicates
let unique: HashSet<i32> = vec![1, 2, 2, 3, 3, 3].into_iter().collect();
println!("{}", unique.len()); // 3
// HashMap — collect from an iterator of (key, value) tuples
let map: HashMap<&str, i32> = vec![("one", 1), ("two", 2), ("three", 3)]
.into_iter()
.collect();
println!("{}", map["two"]); // 2
// partition — split one iterator into two Vecs based on a predicate
let (evens, odds): (Vec<_>, Vec<_>) = (0..10).partition(|&x| x % 2 == 0);
println!("{:?}", evens); // [0, 2, 4, 6, 8]
println!("{:?}", odds); // [1, 3, 5, 7, 9]
}