Lesson 12

Collections

Work with Vec, String, and HashMap -- Rust's most commonly used collection types.

Vec: Growable Arrays

Vec<T> is Rust's growable array type. Create one with vec![] or Vec::new(), add items with .push(), and access elements by index or with .get(). Vectors own their data and free it when they go out of scope.

HashMap: Key-Value Pairs

HashMap<K, V> stores key-value pairs for fast lookups. Insert with .insert(), look up with .get() (returns Option), and iterate with a for loop. You need to bring it in with use std::collections::HashMap.

Iterators

Rust's iterators are lazy and composable. Chain methods like .iter(), .map(), .filter(), and .collect() to transform collections in clean, readable pipelines. They compile down to the same performance as hand-written loops.

Try it

use std::collections::HashMap;

fn main() {
    // --- Vec<T> ---
    println!("--- Vectors ---");
    let mut fruits = vec!["apple", "banana", "cherry"];
    println!("  Fruits: {:?}", fruits);

    fruits.push("date");
    fruits.push("elderberry");
    println!("  After push: {:?}", fruits);
    println!("  Length: {}", fruits.len());
    println!("  First: {}", fruits[0]);

    // Safe access with .get()
    match fruits.get(99) {
        Some(f) => println!("  Index 99: {}", f),
        None => println!("  Index 99: out of bounds (but no crash!)"),
    }

    // --- Iterators ---
    println!("\n--- Iterator Magic ---");
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let evens: Vec<&i32> = numbers.iter().filter(|&&n| n % 2 == 0).collect();
    println!("  Evens: {:?}", evens);

    let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
    println!("  Doubled: {:?}", doubled);

    let sum: i32 = numbers.iter().sum();
    println!("  Sum of 1..10: {}", sum);

    // Chaining: sum of squares of even numbers
    let sum_of_even_squares: i32 = numbers
        .iter()
        .filter(|&&n| n % 2 == 0)
        .map(|n| n * n)
        .sum();
    println!("  Sum of even squares: {}", sum_of_even_squares);

    // --- HashMap ---
    println!("\n--- HashMap ---");
    let mut scores: HashMap<&str, i32> = HashMap::new();
    scores.insert("Alice", 95);
    scores.insert("Bob", 87);
    scores.insert("Charlie", 92);

    // HashMap does not keep insertion order, so this line can print in any order
    println!("  Scores: {:?}", scores);

    // Look up a value
    if let Some(score) = scores.get("Alice") {
        println!("  Alice's score: {}", score);
    }

    // Update a value
    scores.insert("Bob", 91);
    println!("  Bob's updated score: {}", scores["Bob"]);

    // entry API: insert only if key is missing
    scores.entry("Diana").or_insert(88);
    scores.entry("Alice").or_insert(0); // won't overwrite
    println!("  After entry: {:?}", scores);

    // --- Word count: a classic HashMap example ---
    println!("\n--- Word Count ---");
    let text = "the quick brown fox jumps over the lazy fox";
    let mut word_count: HashMap<&str, i32> = HashMap::new();

    for word in text.split_whitespace() {
        let count = word_count.entry(word).or_insert(0);
        *count += 1;
    }

    let mut counts: Vec<(&&str, &i32)> = word_count.iter().collect();
    counts.sort_by_key(|&(word, _)| *word);
    for (word, count) in counts {
        println!("  {}: {}", word, count);
    }
}

Challenge

Modify the word count example to count character frequency in a string instead. Use .chars() to iterate over characters.

Previous: Error Handling

Next: Traits