If you've come from Python or JavaScript, you might think "just use a list" and call it a day. But Rust has multiple collection types, and choosing the right one matters.
The big three are: Vec (growable arrays), HashMap (key-value stores), and &[T] (slices, aka "views" into existing data).
Vec — When You Need a List
A Vec is a growable array. Unlike arrays in many languages, it can resize:
let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
// numbers is now [1, 2, 3]
You can also use the vec! macro for a shorthand:
let numbers = vec![1, 2, 3, 4, 5];
Common operations:
// Get by index (returns Option)
let first = numbers.get(0); // Some(&1)
let hundred = numbers.get(100); // None
// Iterate
for n in &numbers {
println!("{}", n);
}
// Modify
numbers.push(6);
numbers.remove(0); // Remove first element
// Check length
println!("{}", numbers.len()); // 5
When to Use Vec
- Ordered access by index — You need to access elements by position
- Iteration in order — You're processing elements sequentially
- Growing/shrinking — The collection size changes at runtime
HashMap — When You Need a Dictionary
A HashMap stores key-value pairs. Fast lookup by key:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("Alice", 100);
scores.insert("Bob", 85);
scores.insert("Charlie", 92);
// Lookup
if let Some(score) = scores.get("Alice") {
println!("Alice's score: {}", score);
}
// Update
scores.insert("Alice", 110); // Overwrites
// Or use entry API for conditional updates
scores.entry("Bob").and_modify(|s| *s += 10);
When to Use HashMap
- Fast lookup by key — O(1) instead of O(n)
- Key-value associations — You're mapping keys to values
- Deduplication — Insert and check existence in one operation
The Slice — When You Don't Own
A slice (&[T]) is a view into existing data. It doesn't own anything:
let array = [1, 2, 3, 4, 5];
let slice = &array[1..4]; // [2, 3, 4]
fn process(slice: &[i32]) {
for n in slice {
println!("{}", n);
}
}
Functions accept slices when they don't need to own the data. This is why so many standard library functions take &[T] — they work on arrays, vectors, or any contiguous sequence.
Choosing Between Vec and HashMap
| Operation | Vec | HashMap | |-----------|-----|---------| | Insert at end | O(1) amortized | N/A | | Lookup by index | O(1) | N/A | | Lookup by key | O(n) | O(1) | | Iterate | O(n) | O(n) | | Sorted? | No (by default) | No |
Rule of thumb: If you're doing more lookups by key than insertions, use HashMap. If you're accessing by position, use Vec.
Other Collections Worth Knowing
- VecDeque — Double-ended queue. Fast insert/remove at both ends.
- BTreeMap — Sorted key-value pairs. Slower than HashMap but sorted iteration.
- BTreeSet — Sorted unique values.
- BinaryHeap — Priority queue (max-heap).
You won't need these immediately, but they exist when you need them.
Your Turn
Write a program that:
- Takes a list of fruit names
- Counts how many times each fruit appears
- Prints the results sorted alphabetically
Use a HashMap to count, then convert to a Vec, sort it, and print.
let fruits = vec!["apple", "banana", "apple", "orange", "banana", "apple"];
// Expected output:
// apple: 3
// banana: 2
// orange: 1
Next up: Chapter 8 — Lifetimes. The part that scares everyone, demystified.