Rust - Collections
Modern Rust: Common Collections
Section titled “Modern Rust: Common 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).
Vector: A Resizable List
Section titled “Vector: A Resizable List”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.
Creating and Manipulating Vectors
Section titled “Creating and Manipulating Vectors”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);}Common Vec<T> Methods
Section titled “Common Vec<T> Methods”| Method | Description |
|---|---|
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. |
HashMap: Key-Value Storage
Section titled “HashMap: Key-Value Storage”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.
Creating and Using HashMaps
Section titled “Creating and Using HashMaps”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); }}Best Practice: The entry API
Section titled “Best Practice: The entry API”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);}HashSet: A Collection of Unique Values
Section titled “HashSet: A Collection of Unique Values”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.
Creating and Using HashSets
Section titled “Creating and Using HashSets”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!"); }}Set Operations
Section titled “Set Operations”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<_>>());}Choosing the Right Collection
Section titled “Choosing the Right Collection”- 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.