Collections #
Nearly every useful program stores and processes a collection of data. Rust provides several collection data structures in the standard library, each with different performance characteristics and use cases. Unlike languages that provide one general-purpose “list” type, Rust requires you to choose the most appropriate collection for your needs — a choice that has a direct impact on performance and code clarity. This article discusses the most frequently used collections: Vec for sequential data, HashMap and HashSet for fast lookups, BTreeMap and BTreeSet for ordered data, and VecDeque for queuing needs.
Vec<T> — Most Common Sequential Collection #
Vec<T> is a dynamic array that stores elements of type T sequentially on the heap. This is the most frequently used collection in Rust — the default choice when you need a list of elements.
fn main() {
// Create Vec
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3, 4, 5]; // macro vec! — the most common way
let v3: Vec<i32> = (1..=5).collect(); // of iterators
let v4 = vec![0i32; 10]; // 10 elements have a value of 0
// By capacity — avoid reallocation if final size is known
let mut v5: Vec<i32> = Vec::with_capacity(100);
println!("len: {}, capacity: {}", v5.len(), v5.capacity()); // 0.100
// Add elements
v.push(1);
v.push(2);
v.push(3);
// Add multiple elements at once
v.extend([4, 5, 6]);
v.extend(v2.iter().copied());
// Insert at a specific position — O(n), shift all elements after it
v.insert(0, 99); // inserts 99 at index 0
println!("{:?}", v); // [99, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]
// Delete element
let terakhir = v.pop(); // delete and return last element: Some(5)
let di_posisi = v.remove(0); // delete at index 0, shift other elements: 99
v.swap_remove(0); // delete at index 0, fill with last element — O(1)
// Element access
let pertama: Option<&i32> = v.first();
let terakhir: Option<&i32> = v.last();
let ketiga: Option<&i32> = v.get(2); // safe — returns Option
let langsung: &i32 = &v[2]; // panic if outside the limit
// ANTI-PATTERN: direct access without bounds checking
// let x = v[100]; // panic if v has < 101 elements
// CORRECT: use get() for secure access
if let Some(x) = v.get(100) {
println!("{}", x);
}
}
Slice operation on Vec #
Vec<T> can be derefed to &[T] (slice), which opens access to all slice methods.
fn main() {
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
// Sequencing
v.sort(); // ascending order: [1, 1, 2, 3, 3, 4, 5, 5, 6, 9]
v.sort_by(|a, b| b.cmp(a)); // descending order
v.sort_by_key(|&x| std::cmp::Reverse(x)); // another way of descending
// For float (not implements Ord)
let mut float_v = vec![3.1f64, 1.4, 2.7, 0.8];
float_v.sort_by(|a, b| a.partial_cmp(b).unwrap());
// Search — binary search only for sorted Vecs
v.sort();
match v.binary_search(&5) {
Ok(idx) => println!("Ditemukan di indeks: {}", idx),
Err(idx) => println!("Tidak ada, bisa disisipkan di indeks: {}", idx),
}
// Deduplication — remove sequential duplicates (effective after sort)
v.dedup();
println!("{:?}", v); // [1, 2, 3, 4, 5, 6, 9]
// Retain — retain only what meets the condition (in-place filter)
let mut v2 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
v2.retain(|&x| x % 2 == 0);
println!("{:?}", v2); // [2, 4, 6, 8, 10]
// Reverse order
v2.reverse();
println!("{:?}", v2); // [10, 8, 6, 4, 2]
// Slice — reference to Vec section
let v3 = vec![1, 2, 3, 4, 5];
let tengah: &[i32] = &v3[1..4]; // [2, 3, 4]
println!("{:?}", tengah);
// Windows and chunks
for window in v3.windows(3) {
println!("{:?}", window); // [1,2,3], [2,3,4], [3,4,5]
}
for chunk in v3.chunks(2) {
println!("{:?}", chunk); // [1,2], [3,4], [5]
}
// Split — splits Vec in two at certain positions
let (kiri, kanan) = v3.split_at(2);
println!("{:?} {:?}", kiri, kanan); // [1, 2] [3, 4, 5]
// Truncate and clear
let mut v4 = vec![1, 2, 3, 4, 5];
v4.truncate(3); // keep only first 3 elements: [1, 2, 3]
v4.clear(); // remove all elements, fixed capacity
}
Memory Management Vec #
fn main() {
// Vec reallocates when capacity is exhausted
// Strategy: capacity is doubled every time it runs out (amortized O(1))
let mut v: Vec<i32> = Vec::new();
for i in 0..10 {
v.push(i);
println!("len: {}, cap: {}", v.len(), v.capacity());
// cap: 1, 2, 4, 4, 8, 8, 8, 8, 16, 16 — grows exponentially
}
// with_capacity if final size is known
let n = 1000;
let mut v_efisien: Vec<i32> = Vec::with_capacity(n);
for i in 0..n as i32 {
v_efisien.push(i); // never reallocates
}
// shrink_to_fit — restore unused memory
v_efisien.truncate(10);
println!("Sebelum shrink — cap: {}", v_efisien.capacity()); // is still 1000
v_efisien.shrink_to_fit();
println!("Setelah shrink — cap: {}", v_efisien.capacity()); // about 10
// drain — delete range elements, return as iterator
let mut v5 = vec![1, 2, 3, 4, 5, 6, 7, 8];
let diambil: Vec<i32> = v5.drain(2..5).collect();
println!("Diambil: {:?}", diambil); // [3, 4, 5]
println!("Sisa: {:?}", v5); // [1, 2, 6, 7, 8]
}
HashMap<K, V> — Fast Lookup with Keys #
HashMap<K, V> stores key-value pairs with an average lookup time of O(1). This is the default choice when you need to search for a value based on a key.
use std::collections::HashMap;
fn main() {
// Create HashMap
let mut skor: HashMap<String, i32> = HashMap::new();
// Add entry
skor.insert(String::from("Alice"), 85);
skor.insert(String::from("Bob"), 92);
skor.insert(String::from("Carol"), 78);
// From array of tuples
let map: HashMap<&str, i32> = [("satu", 1), ("dua", 2), ("tiga", 3)]
.into_iter()
.collect();
// Read value
let alice_skor = skor.get("Alice"); // Option<&i32> — safe
let bob_skor = skor["Bob"]; // i32 — panic if it's not there
println!("{:?}", alice_skor); // Some(85)
// Key presence check
println!("{}", skor.contains_key("Alice")); // true
println!("{}", skor.contains_key("Dave")); // false
// Delete entry
let dihapus = skor.remove("Carol"); // returns Option<V>
println!("{:?}", dihapus); // Some(78)
// Iteration
for (nama, nilai) in &skor {
println!("{}: {}", nama, nilai);
}
// Key only or value only
let semua_nama: Vec<&String> = skor.keys().collect();
let semua_skor: Vec<&i32> = skor.values().collect();
// Size
println!("Jumlah entri: {}", skor.len());
println!("Kosong: {}", skor.is_empty());
}
Entry API — The Most Important Patterns in HashMap #
The ZZL29ZZ Entry API is an idiomatic way to handle the “insert if it doesn’t already exist” or “update if it already exists” situation without a double lookup.
Entry API is an idiomatic way to handle the “insert if it doesn’t already exist” or “update if it already exists” situation without a double lookup.
use std::collections::HashMap;
fn main() {
let mut skor: HashMap<&str, i32> = HashMap::new();
// ANTI-PATTERN: double lookup — check for existence then insert
if !skor.contains_key("Alice") {
skor.insert("Alice", 0);
}
*skor.get_mut("Alice").unwrap() += 10;
// CORRECT: API entry — one lookup, more efficiency
skor.entry("Alice").or_insert(0);
*skor.entry("Alice").or_insert(0) += 10;
// or_insert_with — lazy, value is only created if it doesn't already exist
skor.entry("Bob").or_insert_with(|| {
println!("Membuat nilai default untuk Bob");
calculate_default_score()
});
// or_default — use the Default trait value
skor.entry("Carol").or_default(); // 0 for i32
// and_modify — modify if present, no insert if absent
skor.entry("Alice").and_modify(|v| *v += 5);
// Combination: modify if present, insert with default if not
skor.entry("Dave")
.and_modify(|v| *v += 10)
.or_insert(50);
println!("{:?}", skor);
// Classic use case: calculating frequencies
let teks = "apel pisang apel jeruk pisang apel mangga";
let mut frekuensi: HashMap<&str, u32> = HashMap::new();
for kata in teks.split_whitespace() {
*frekuensi.entry(kata).or_insert(0) += 1;
}
println!("{:?}", frekuensi);
// {"apples": 3, "bananas": 2, "oranges": 1, "mangoes": 1}
// Use case: grouping — group by key
let data = vec![
("backend", "Alice"),
("frontend", "Bob"),
("backend", "Carol"),
("frontend", "Dave"),
("backend", "Eve"),
];
let mut per_tim: HashMap<&str, Vec<&str>> = HashMap::new();
for (tim, nama) in &data {
per_tim.entry(tim).or_insert_with(Vec::new).push(nama);
}
println!("{:?}", per_tim);
// {"backend": ["Alice", "Carol", "Eve"], "frontend": ["Bob", "Dave"]}
}
fn calculate_default_score() -> i32 { 50 }
Hasher Customization #
HashMap uses a cryptographically secure hasher by default (SipHash), which is resistant to HashDoS attacks. For maximum performance in contexts that do not require such security, you can replace the hasher.
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::collections::hash_map::DefaultHasher;
// Uses FxHashMap from the rustc-hash crate for higher performance
// Add to Cargo.toml: rustc-hash="1"
// use rustc_hash::FxHashMap;
fn main() {
// HashMap standard — secure, performs well in most cases
let mut map: HashMap<String, i32> = HashMap::new();
map.insert("kunci".to_string(), 42);
// When to consider alternative hashers:
// - Key is a predictable integer or short string
// - Hot path that needs maximum throughput
// - Non-public context (does not accept external input)
}
HashSet<T> — Collection of Unique Values #
HashSet<T> is HashMap<T, ()> — it stores a unique value without value. Use it when you need to know whether an element exists, regardless of its order.
use std::collections::HashSet;
fn main() {
// Create HashSet
let mut set: HashSet<i32> = HashSet::new();
let set2: HashSet<i32> = vec![1, 2, 3, 2, 1].into_iter().collect(); // duplicate removed
// Add and delete
set.insert(1);
set.insert(2);
set.insert(2); // ignored — already there
set.remove(&1);
// Checking
println!("{}", set.contains(&2)); // true
// Set operations — this is the main strength of HashSet
let a: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();
let b: HashSet<i32> = vec![3, 4, 5, 6, 7].into_iter().collect();
// Intersection — elements present in both
let irisan: HashSet<&i32> = a.intersection(&b).collect();
println!("Irisan: {:?}", irisan); // {3, 4, 5}
// Union — all elements of both sets
let gabungan: HashSet<&i32> = a.union(&b).collect();
println!("Gabungan: {:?}", gabungan); // {1, 2, 3, 4, 5, 6, 7}
// Difference — elements in a but not in b
let selisih: HashSet<&i32> = a.difference(&b).collect();
println!("Selisih a-b: {:?}", selisih); // {1, 2}
// Symmetrical difference — elements that are present in one but not both
let sym_diff: HashSet<&i32> = a.symmetric_difference(&b).collect();
println!("Selisih simetris: {:?}", sym_diff); // {1, 2, 6, 7}
// Checking set relations
let sub: HashSet<i32> = vec![1, 2].into_iter().collect();
println!("sub bagian dari a: {}", sub.is_subset(&a)); // true
println!("a superset dari sub: {}", a.is_superset(&sub)); // true
println!("a dan b disjoint: {}", a.is_disjoint(&b)); // false
// Use case: deduplication with sequence preservation
let dengan_duplikat = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
let mut seen = HashSet::new();
let unik_berurutan: Vec<i32> = dengan_duplikat
.into_iter()
.filter(|x| seen.insert(*x)) // insert returns false if it already exists
.collect();
println!("{:?}", unik_berurutan); // [3, 1, 4, 5, 9, 2, 6] — awake sequence
}
BTreeMap<K, V> and BTreeSet<T> — Ordered Collection #
BTreeMap and BTreeSet store elements in sorted order by key. In contrast to HashMap/HashSet which do not guarantee order, BTreeMap always iterates in key order.
use std::collections::BTreeMap;
fn main() {
let mut map: BTreeMap<String, i32> = BTreeMap::new();
map.insert("charlie".to_string(), 3);
map.insert("alice".to_string(), 1);
map.insert("bob".to_string(), 2);
// Iteration is always in key order (alphabetical for String)
for (k, v) in &map {
println!("{}: {}", k, v);
}
// alice: 1
// bob: 2
// charlie: 3
// Range query — this is what HashMap can't do
for (k, v) in map.range("alice".to_string()..="bob".to_string()) {
println!("{}: {}", k, v);
}
// alice: 1
// bob: 2
// Smallest and largest elements
println!("{:?}", map.first_key_value()); // Some(("alice", 1))
println!("{:?}", map.last_key_value()); // Some(("charlie", 3))
// Remove the smallest or largest element
map.pop_first(); // remove "alice"
map.pop_last(); // remove "charlie"
// Entry API is available the same as HashMap
map.entry("dave".to_string()).or_insert(4);
}
use std::collections::BTreeSet;
fn main() {
let mut set: BTreeSet<i32> = BTreeSet::new();
for x in [5, 2, 8, 1, 9, 3, 7, 4, 6] {
set.insert(x);
}
// Iterations are always in sorted order
println!("{:?}", set.iter().collect::<Vec<_>>()); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Range query
let rentang: Vec<&i32> = set.range(3..=7).collect();
println!("{:?}", rentang); // [3, 4, 5, 6, 7]
// Smallest and largest values
println!("{:?}", set.first()); // Some(1)
println!("{:?}", set.last()); // Some(9)
// Set operations (same as HashSet)
let a: BTreeSet<i32> = vec![1, 2, 3, 4].into_iter().collect();
let b: BTreeSet<i32> = vec![3, 4, 5, 6].into_iter().collect();
let irisan: BTreeSet<i32> = a.intersection(&b).copied().collect();
println!("{:?}", irisan); // {3, 4}
}
flowchart TD
A{Perlu lookup berdasarkan kunci?} -- Yes --> B{Perlu urutan terurut?}
A -- No --> C{Perlu nilai unik saja?}
B -- Yes --> D["BTreeMap<K,V>\nO(log n), terurut, range query"]
B -- No --> E["HashMap<K,V>\nO(1) rata-rata, tidak terurut"]
C -- Ya, terurut --> F["BTreeSet<T>\nO(log n), terurut, range query"]
C -- Ya, tidak terurut --> G["HashSet<T>\nO(1) rata-rata, tidak terurut"]
C -- No --> H["Vec<T>\nO(1) akses indeks, berurutan"]
style D fill:#e8f5e9
style E fill:#e3f2fd
style F fill:#e8f5e9
style G fill:#e3f2fd
style H fill:#fff3e0VecDeque<T> — Two-Ended Queue #
VecDeque<T> is a double-ended queue — it supports additions and deletions at both ends efficiently (O(1)). Use it when you need an efficient queue (FIFO) or stack (LIFO) at both ends.
use std::collections::VecDeque;
fn main() {
let mut deque: VecDeque<i32> = VecDeque::new();
// Adds in front and rear
deque.push_back(1);
deque.push_back(2);
deque.push_back(3);
deque.push_front(0);
deque.push_front(-1);
println!("{:?}", deque); // [-1, 0, 1, 2, 3]
// Wipe from front and back
let depan = deque.pop_front(); // Some(-1)
let belakang = deque.pop_back(); // Some(3)
println!("{:?}", deque); // [0, 1, 2]
// Access without deleting
println!("{:?}", deque.front()); // Some(0)
println!("{:?}", deque.back()); // Some(2)
// Convert to Vec (ring buffer linearization)
let sebagai_vec: Vec<i32> = Vec::from(deque.clone());
// Or: deque.make_contiguous() to get &mut [T]
// Use case: sliding window / buffer with fixed size
fn rata_rata_bergerak(data: &[f64], ukuran_window: usize) -> Vec<f64> {
let mut window: VecDeque<f64> = VecDeque::with_capacity(ukuran_window);
let mut hasil = Vec::new();
for &nilai in data {
window.push_back(nilai);
if window.len() > ukuran_window {
window.pop_front();
}
if window.len() == ukuran_window {
let rata: f64 = window.iter().sum::<f64>() / ukuran_window as f64;
hasil.push(rata);
}
}
hasil
}
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
let ma = rata_rata_bergerak(&data, 3);
println!("{:?}", ma); // [2.0, 3.0, 4.0, 5.0, 6.0]
// Use case: BFS (Breadth-First Search)
fn bfs(graf: &Vec<Vec<usize>>, mulai: usize) -> Vec<usize> {
let n = graf.len();
let mut dikunjungi = vec![false; n];
let mut antrean: VecDeque<usize> = VecDeque::new();
let mut urutan = Vec::new();
antrean.push_back(mulai);
dikunjungi[mulai] = true;
while let Some(node) = antrean.pop_front() {
urutan.push(node);
for &tetangga in &graf[node] {
if !dikunjungi[tetangga] {
dikunjungi[tetangga] = true;
antrean.push_back(tetangga);
}
}
}
urutan
}
}
Collection Performance Comparison #
Choosing the right collection has a significant impact on performance. Here’s a comparison of the complexity of the main operations:
| Operation | Vec | HashMap | BTreeMap | VecDeque |
|---|---|---|---|---|
| Access per index | O(1) | — | — | O(1) |
| Lookup per key | O(n) | O(1) avg | O(log n) | O(n) |
| Insert at the end | O(1) amortized | O(1) avg | O(log n) | O(1) |
| Insert in front | O(n) | — | — | O(1) |
| Insert in the middle | O(n) | — | — | O(n) |
| Delete at the end | O(1) | — | — | O(1) |
| Delete in front | O(n) | — | — | O(1) |
| Ordered iteration | O(n) | O(n) unordered | O(n) sorted | O(n) |
| Range query | O(n) | — | O(log n + k) | — |
| Memory overhead | Low | Medium | Height | Low |
flowchart TD
A[Butuh koleksi apa?] --> B{Akses berdasarkan posisi/indeks?}
B -- Yes --> C{Insert/hapus di depan sering?}
C -- Yes --> D[VecDeque — double-ended queue]
C -- No --> E[Vec — dynamic array]
B -- No --> F{"Need a fast O(1) lookup?"}
F -- Ya, kunci ke nilai --> G{Urutan penting?}
F -- Ya, hanya keanggotaan --> H{Urutan penting?}
F -- No --> I[Vec with linear search]
G -- Yes --> J[BTreeMap — sorted, range query]
G -- No --> K[HashMap — fastest for lookups]
H -- Yes --> L[BTreeSet — ordered set]
H -- No --> M[HashSet — fastest set]
style D fill:#fff3e0
style E fill:#e3f2fd
style J fill:#e8f5e9
style K fill:#e3f2fd
style L fill:#e8f5e9
style M fill:#e3f2fdIdiomatic Patterns with Collections #
Some patterns that often appear in production Rust code when working with collections.
use std::collections::HashMap;
fn main() {
// 1. HashMap transformation — map values
let harga: HashMap<&str, f64> = [("apel", 5000.0), ("pisang", 3000.0)].into_iter().collect();
let setelah_pajak: HashMap<&str, f64> = harga.iter()
.map(|(&k, &v)| (k, v * 1.1))
.collect();
// 2. HashMap Filter
let mahal: HashMap<&&str, &f64> = harga.iter()
.filter(|(_, &v)| v > 4000.0)
.collect();
// 3. Invert HashMap — swap keys and values
let kode: HashMap<&str, u32> = [("merah", 1), ("hijau", 2), ("biru", 3)].into_iter().collect();
let terbalik: HashMap<u32, &str> = kode.iter().map(|(&k, &v)| (v, k)).collect();
println!("{:?}", terbalik); // {1: "red", 2: "green", 3: "blue"}
// 4. Merge two HashMap — the value from the second map wins if there is a conflict
let mut map1: HashMap<&str, i32> = [("a", 1), ("b", 2)].into_iter().collect();
let map2: HashMap<&str, i32> = [("b", 20), ("c", 3)].into_iter().collect();
map1.extend(map2);
println!("{:?}", map1); // {"a": 1, "b": 20, "c": 3}
// 5. Calculate top-N of frequencies
let teks = "apel pisang apel jeruk pisang apel mangga jeruk apel";
let mut frekuensi: Vec<(&str, usize)> = teks.split_whitespace()
.fold(HashMap::<&str, usize>::new(), |mut map, kata| {
*map.entry(kata).or_insert(0) += 1;
map
})
.into_iter()
.collect();
frekuensi.sort_by(|a, b| b.1.cmp(&a.1)); // in order of most
let top_2: Vec<_> = frekuensi.iter().take(2).collect();
println!("{:?}", top_2); // [("apples", 4), ("bananas", 2)] or ("oranges", 2)
// 6. Vec of struct — sort by field
#[derive(Debug)]
struct Produk { nama: String, harga: f64, stok: u32 }
let mut produk = vec![
Produk { nama: "Apel".to_string(), harga: 5000.0, stok: 100 },
Produk { nama: "Pisang".to_string(), harga: 3000.0, stok: 50 },
Produk { nama: "Jeruk".to_string(), harga: 7000.0, stok: 75 },
];
// Sort by ascending price
produk.sort_by(|a, b| a.harga.partial_cmp(&b.harga).unwrap());
// Sort by multiple criteria
produk.sort_by(|a, b| {
b.stok.cmp(&a.stok) // descending stock
.then(a.harga.partial_cmp(&b.harga).unwrap()) // then ascending price
});
// 7. Flatten and deduplication from multiple sources
let sumber1 = vec![1, 2, 3, 4];
let sumber2 = vec![3, 4, 5, 6];
let sumber3 = vec![5, 6, 7, 8];
use std::collections::HashSet;
let semua_unik: HashSet<i32> = [sumber1, sumber2, sumber3]
.into_iter()
.flatten()
.collect();
let mut terurut: Vec<i32> = semua_unik.into_iter().collect();
terurut.sort();
println!("{:?}", terurut); // [1, 2, 3, 4, 5, 6, 7, 8]
}
When to Use the Mana Collection #
Gunakan Vec jika:
✓ Kamu menyimpan daftar elemen berurutan
✓ Akses berdasarkan indeks adalah operasi utama
✓ Insert dan hapus hampir selalu di akhir
✓ Kamu membutuhkan representasi memori yang compact
✓ Kamu perlu mengirim data ke fungsi yang menerima slice &[T]
Gunakan HashMap jika:
✓ Kamu perlu lookup O(1) berdasarkan kunci
✓ Urutan kunci tidak penting
✓ Kunci adalah tipe yang implements Hash + Eq
Gunakan BTreeMap jika:
✓ Kamu perlu iterasi dalam urutan kunci terurut
✓ Kamu perlu range query (ambil semua entri dalam rentang kunci)
✓ Kamu perlu min/max kunci secara efisien
✓ Kunci tidak implements Hash (misalnya float)
Gunakan HashSet jika:
✓ Kamu hanya perlu mengetahui apakah nilai ada atau tidak
✓ Kamu perlu operasi himpunan (irisan, gabungan, selisih)
✓ Deduplikasi cepat dari koleksi besar
Gunakan VecDeque jika:
✓ Kamu membutuhkan antrean FIFO (First In, First Out)
✓ Insert dan hapus terjadi di kedua ujung
✓ Implementasi sliding window atau buffer circular
Jangan gunakan LinkedList kecuali:
✗ Kamu perlu O(1) insert/hapus di tengah dengan cursor yang sudah ada
✗ (Sangat jarang di Rust — Vec hampir selalu lebih baik karena cache locality)
LinkedListis in the Rust standard library but is rarely the best choice. Sequential access onVecis much more cache-friendly thanLinkedListbecause data is stored close together in memory. Even for middle insert operations,Vecis often faster in practice due to better cache locality — unless the data size is very large and middle insert operations are very frequent.
Summary #
Vec<T>is the default choice — use for ordered lists, per-index access, and when insert/delete is almost always at the end. Usewith_capacityif the final size is known to avoid reallocation.HashMap<K,V>for O(1) lookup — API entry (or_insert,and_modify) is an idiomatic way to insert-or-update without a double lookup. Use for frequency counting and grouping.BTreeMap<K,V>if order matters — the only map collection that guarantees ordered iteration and supports range queries. Trade-off: O(log n) vs O(1) for HashMap.HashSet<T>for membership and set operations —insertreturnsfalseif it already exists, which can be exploited for deduplication while preserving order.VecDeque<T>for queue — O(1) on both ends. Great choice for BFS, sliding window, and fixed size buffers.- API entry is a HashMap key — avoid the
contains_key+insertpattern that performs two lookups. Useentry().or_insert()orentry().and_modify().or_insert().- Select by dominant operation — O(1) vs O(log n) sounds small, but on a hot path with millions of operations the difference is significant. Profile first before premature optimization.
retainfor in-place filter — more efficient than filter + collect to new Vec when you want to modify an existing collection without a new allocation.