#12 Collections
Vec, HashMap, iterators
Vectors (Vec<T>)
A Vec<T> is a growable array. It stores elements of the same type on the heap and is the most commonly used collection in Rust.
// Create an empty vector and push elements
let mut fruits: Vec<&str> = Vec::new();
fruits.push("apple");
fruits.push("banana");
fruits.push("cherry");
println!("Fruits: {:?}", fruits);
// Create with the vec! macro
let numbers = vec![1, 2, 3, 4, 5];
// Access elements
println!("First: {}", numbers[0]); // panics if out of bounds
println!("Safe: {:?}", numbers.get(10)); // returns Option<&T>Common vector methods include push(), pop(), len(), is_empty(), contains(), and remove().
let mut nums = vec![10, 20, 30, 40];
nums.push(50); // Add to end
let last = nums.pop(); // Remove from end -> Some(50)
nums.remove(1); // Remove at index 1 -> 20
println!("Length: {}", nums.len()); // 3
println!("Has 10: {}", nums.contains(&10)); // trueHashMaps
A HashMap<K, V> stores key-value pairs. Keys must implement Eq and Hash. You need to import it from std::collections.
use std::collections::HashMap;
let mut scores: HashMap<&str, i32> = HashMap::new();
scores.insert("Alice", 95);
scores.insert("Bob", 87);
scores.insert("Charlie", 92);
// Access values
if let Some(score) = scores.get("Alice") {
println!("Alice scored: {}", score);
}
// Iterate over key-value pairs
for (name, score) in &scores {
println!("{}: {}", name, score);
}The entry() API lets you insert a value only if the key does not already exist:
let mut word_count: HashMap<&str, i32> = HashMap::new();
let words = vec!["hello", "world", "hello", "rust"];
for word in words {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", word_count);
// {"hello": 2, "world": 1, "rust": 1}Iterators
Iterators are one of Rust's most powerful features. Any type that implements the Iterator trait can be used in a for loop and with iterator adapter methods.
let numbers = vec![1, 2, 3, 4, 5];
// iter() borrows each element
for n in numbers.iter() {
println!("{}", n);
}
// into_iter() takes ownership
for n in numbers.into_iter() {
println!("{}", n);
}
// iter_mut() borrows each element mutably
let mut values = vec![1, 2, 3];
for n in values.iter_mut() {
*n *= 2;
}
println!("{:?}", values); // [2, 4, 6]Common iterator methods
Iterator adapters transform iterators. They are lazy: nothing happens until you consume the iterator with a method like collect() or sum().
let numbers = vec![1, 2, 3, 4, 5];
// map: transform each element
let doubled: Vec<i32> = numbers.iter()
.map(|&n| n * 2)
.collect();
println!("Doubled: {:?}", doubled); // [2, 4, 6, 8, 10]
// filter: keep elements matching a predicate
let evens: Vec<i32> = (1..=10)
.filter(|n| n % 2 == 0)
.collect();
println!("Evens: {:?}", evens); // [2, 4, 6, 8, 10]
// sum: consume the iterator into a total
let total: i32 = numbers.iter().sum();
println!("Sum: {}", total); // 15You can chain multiple adapters together to build powerful data pipelines:
let words = vec!["hello", "world", "rust", "is", "great"];
let result: Vec<String> = words.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", result); // ["HELLO", "WORLD", "RUST", "GREAT"]Summary
// Vectors
let mut v = Vec::new(); // or vec![1, 2, 3]
v.push(value); // add element
v.pop(); // remove last
v.get(index); // safe access -> Option
// HashMaps
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert(key, value); // add entry
m.get(&key); // lookup -> Option
m.entry(key).or_insert(v); // insert if absent
// Iterators
.iter() // borrow elements
.map(|x| y) // transform
.filter(|x| bool) // keep matching
.collect() // gather into collection
.sum() // total numeric valuesYour turn
Try running the collections demo with cargo run and verify it compiles cleanly with cargo check: