Rust Essential Course – Lesson 8 – Collections and Iterators

Lesson 8: Collections and Iterators

Introduction

So far you’ve worked with single values and simple structs. Real programs need to store and process groups of data. Rust’s standard library provides three essential collections: Vec, String, and HashMap. Each has its own strengths, and Rust’s iterator system makes working with them expressive and efficient.

Vectors: Growable Lists

A Vec<T> is a growable, ordered list of values of the same type. It’s the most commonly used collection in Rust.

fn main() {
    // Create a vector
    let mut scores = Vec::new();
    scores.push(10);
    scores.push(20);
    scores.push(30);

    // Or use the vec! macro
    let fruits = vec!["apple", "banana", "cherry"];

    // Access elements
    println!("First fruit: {}", fruits[0]);
    println!("Score count: {}", scores.len());
}
  • Use push() to add elements and len() to get the count.
  • The vec! macro is a convenient shorthand for creating vectors with initial values.
  • Index with [i] to access elements — Rust will panic if the index is out of bounds.

Strings

Rust has two string types: &str (a string slice, usually hardcoded) and String (a growable, heap-allocated string). Most of the time when building or modifying text, you’ll use String.

fn main() {
    let mut greeting = String::from("Hello");
    greeting.push_str(", Rustacean!"); // append a string slice
    greeting.push('!');               // append a single char

    println!("{}", greeting);

    // Concatenation with format!
    let s1 = String::from("foo");
    let s2 = String::from("bar");
    let s3 = format!("{}{}", s1, s2); // no ownership moved
    println!("{}", s3);

    // String length and contains
    let word = String::from("Rustacean");
    println!("Length: {}", word.len());
    println!("Contains 'Rust': {}", word.contains("Rust"));
}
  • push_str() appends a &str; push() appends a single char.
  • Use format!() to concatenate strings without moving ownership.
  • Useful methods: len(), contains(), to_uppercase(), trim().

Hash Maps: Key-Value Storage

A HashMap stores data as key-value pairs. It’s perfect for lookups, counting, and grouping data.

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    scores.insert(String::from("Alice"), 95);
    scores.insert(String::from("Bob"),   82);
    scores.insert(String::from("Carol"), 88);

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

    // Update a value
    scores.insert(String::from("Bob"), 90); // overwrites

    // Only insert if key doesn't exist
    scores.entry(String::from("Dave")).or_insert(75);

    println!("Bob's updated score: {}", scores["Bob"]);
}
  • Import with use std::collections::HashMap; before using it.
  • get() returns an Option — use if let to safely unwrap it.
  • entry().or_insert() is the idiomatic way to insert a default value only when the key is absent.

Iterating Over Collections

Rust’s iterator system lets you process collections in a clean, expressive way using methods like for, map, filter, and collect.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // Iterate with a for loop
    for n in &numbers {
        print!("{} ", n);
    }
    println!();

    // map: transform each element
    let doubled = numbers.iter().map(|x| x * 2).collect::<Vec<_>>();
    println!("Doubled: {:?}", doubled);

    // filter: keep only matching elements
    let evens = numbers.iter().filter(|x| *x % 2 == 0).copied().collect::<Vec<_>>();
    println!("Evens: {:?}", evens);

    // sum
    let total: i32 = numbers.iter().sum();
    println!("Sum: {}", total);
}

    • Use &numbers in a for loop to borrow — so you can still use the vector after.

    • iter() produces references; chain .map(), .filter(), and .collect() to build pipelines.

    • Use the turbofish syntax .collect::<Vec<_>>() or a type annotation to tell Rust which collection to produce.

Interactive Task 1: Vector Operations

Create a vector of integers from 1 to 10. Use iterator methods to: print all elements, collect only odd numbers into a new vector, and print the product of all elements.

fn main() {
    let numbers = (1..=10).collect::<Vec<_>>();

    // 1. Print all elements
    // Your code here

    // 2. Collect odd numbers into a new Vec
    // Your code here

    // 3. Print the product of all elements
    // Your code here
}

Try here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=98287973dd8cdaa893ac22b9b47f6a37

Interactive Task 2: Word Counter

Use a HashMap to count how many times each word appears in a sentence. Split the sentence into words using .split_whitespace() and use entry().or_insert(0) to count occurrences.

use std::collections::HashMap;

fn main() {
    let sentence = "the quick brown fox jumps over the lazy dog the fox";

    let mut word_count = HashMap::new();

    for word in sentence.split_whitespace() {
        // Your code here: count each word
    }

    // Print the word counts
    for (word, count) in &word_count {
        println!("{}: {}", word, count);
    }
}

Try here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=95a46a7d7802c4fb342c29a094adca2c

Mini-Challenge

Write a function top_scorer that takes a HashMap of names and scores, and returns the name of the person with the highest score. Call it from main and print the result.

    • Hint: use .iter() and .max_by_key() on the HashMap.

use std::collections::HashMap;

fn top_scorer(scores: &HashMap<String, i32>) -> &str {
    // Your code here
}

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Alice"), 95);
    scores.insert(String::from("Bob"),   82);
    scores.insert(String::from("Carol"), 99);

    println!("Top scorer: {}", top_scorer(&scores));
}

Solution: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f168b72b0e3dc1a192efa2a61e83dc76

Recap

    • Vec is Rust’s go-to growable list — use push(), len(), and indexing to work with it.

    • String is a growable, owned text type — use push_str(), push(), and format!() to build strings.

    • HashMap stores key-value pairs — use insert(), get(), and entry().or_insert() for safe access and updates.

    • Iterators let you chain map(), filter(), sum(), and collect() to process collections expressively.

    • Always use &collection in a for loop to borrow — so the collection is still usable after.

Questions about collections or iterators? Drop a comment!

Leave a Comment

Your email address will not be published. Required fields are marked *