Skip to content

Rust - Collections

Rust’s standard library (std::collections) provides a suite of powerful, efficient, and safe collection types for managing groups of data. We’ll explore the three most essential collections: Vec<T> (vector), HashMap<K, V> (hash map), and HashSet<T> (hash set).

A Vec<T> (pronounced ‘vector’) is a growable list of values of the same type T, stored contiguously in memory. It’s one of the most widely used data structures in Rust.

  • Homogeneous: All elements must be of the same type.
  • Heap-Allocated: The data is stored on the heap, allowing it to grow or shrink at runtime.
  • Ordered: Elements maintain a specific order and are accessed by an index starting at 0.
fn main() {
// Create an empty vector and add elements
let mut numbers: Vec<i32> = Vec::new();
numbers.push(10);
numbers.push(20);
numbers.push(30);
// Create a vector with initial values using the vec! macro
let mut fruits = vec!["Apple", "Banana", "Cherry"];
println!("Initial fruits: {:?}", fruits);
// Accessing elements
// Using .get() is safer as it returns an Option<&T>
match fruits.get(1) {
Some(fruit) => println!("The second fruit is: {}", fruit),
None => println!("There is no second fruit."),
}
// Direct indexing will panic if the index is out of bounds
let first_fruit = &fruits[0];
println!("The first fruit is: {}", first_fruit);
// Iterating and modifying
for fruit in &mut fruits {
*fruit = "Juicy Fruit"; // Modify each element via a mutable reference
}
println!("Modified fruits: {:?}", fruits);
}
MethodDescription
Vec::new()Creates a new, empty vector.
vec![...]A convenient macro to create a vector with initial elements.
push(value)Appends an element to the end of the vector.
pop()Removes and returns the last element as an Option<T>.
len()Returns the number of elements in the vector.
get(index)Returns an Option<&T> for the element at index. Safe.
[index]Returns a reference to the element at index. Panics if out of bounds.

A HashMap<K, V> stores a mapping of keys of type K to values of type V. It uses a hashing function to determine how to store and retrieve data, making lookups, insertions, and deletions extremely fast on average. Keys must be unique.

use std::collections::HashMap;
fn main() {
// Create a new HashMap
let mut scores = HashMap::new();
// Insert key-value pairs
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// Access a value using its key
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);
println!("The score for Blue team is: {}", score);
// Iterate over key-value pairs
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}

The entry API is the idiomatic way to handle complex logic, like inserting a value only if a key doesn’t exist, or updating an existing value.

use std::collections::HashMap;
fn main() {
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
// .entry() returns an Enum that represents a potential entry
// .or_insert(0) gets the value or inserts 0 if it doesn't exist
let count = map.entry(word).or_insert(0);
*count += 1; // Increment the count
}
println!("Word counts: {:?}", map);
}

A HashSet<T> is a collection where every element is unique. It’s implemented as a wrapper around HashMap<T, ()> and is extremely fast for checking if a value is present in the set.

use std::collections::HashSet;
fn main() {
let mut visited_cities = HashSet::new();
// insert() returns true if the value was newly inserted
assert!(visited_cities.insert("Tokyo"));
assert!(visited_cities.insert("London"));
// Inserting a duplicate returns false
assert!(!visited_cities.insert("Tokyo"));
println!("Visited cities: {:?}", visited_cities);
// Check for presence
if visited_cities.contains("London") {
println!("We have been to London!");
}
}

HashSet also supports powerful mathematical set operations.

use std::collections::HashSet;
fn main() {
let set1: HashSet<_> = [1, 2, 3].iter().cloned().collect();
let set2: HashSet<_> = [2, 3, 4].iter().cloned().collect();
// Union: elements in either set
println!("Union: {:?}", set1.union(&set2).collect::<Vec<_>>());
// Intersection: elements in both sets
println!("Intersection: {:?}", set1.intersection(&set2).collect::<Vec<_>>());
// Difference: elements in set1 but not in set2
println!("Difference: {:?}", set1.difference(&set2).collect::<Vec<_>>());
}
  • Use Vec<T> when you need a sequence of elements, in order.
  • Use HashMap<K, V> when you need to associate values with unique keys for fast lookups.
  • Use HashSet<T> when you just need to store unique values and quickly check for their presence.