Collections & Iterators

Course 1 · Ch 8
Collections & Iterators
Vec<T>, HashMap<K,V>, and the functional-style iterator chains that process them

Chapter 2's arrays were fixed-size. This final chapter of Course 1 covers Rust's growable collections — and the iterator adapters that process them in a style that will feel immediately familiar coming from JavaScript.

Vec<T>: A Growable Array

let mut scores: Vec<i32> = Vec::new(); scores.push(90); scores.push(85); let scores2 = vec![90, 85, 78]; // shorthand macro for creating one with initial values for score in &scores2 { println!("{}", score); }

Vec<T> can grow and shrink at runtime, backed by a heap allocation — genuinely similar to Go's own slice type, which is Go's own answer to "an array that can grow." This is a real similarity worth noting, not just another contrast.

HashMap<K, V>: Key-Value Storage

use std::collections::HashMap; let mut ages: HashMap<String, i32> = HashMap::new(); ages.insert(String::from("Dana"), 30); match ages.get("Dana") { Some(age) => println!("Dana is {}", age), None => println!("Not found"), }

get returns Option<&V> — Chapter 6's Option making a direct return appearance. Go's built-in map[string]int is more tightly woven into the language's own syntax, and its lookup returns a (value, ok bool) pair — both languages are explicit about "this key might not exist," just expressed through different mechanisms (Go's two-value return vs. Rust's Option).

Iterator Adapters: map, filter, collect

Rust's iterator methods will feel immediately familiar coming from JavaScript's own array methods — the naming and behavior are genuinely close.

let numbers = vec![1, 2, 3, 4, 5]; let doubled_evens: Vec<i32> = numbers .iter() .filter(|&&n| n % 2 == 0) .map(|&n| n * 2) .collect(); println!("{:?}", doubled_evens); // [4, 8]

.iter() creates an iterator of references (borrowing, Chapter 4) without consuming numbers. .filter() and .map() are lazy — nothing runs until .collect() actually consumes the chain and builds a new Vec.

JavaScriptRust
Transform each elementarray.map(x => ...).iter().map(|x| ...)
Keep matching elementsarray.filter(x => ...).iter().filter(|x| ...)
ExecutionEager — runs immediatelyLazy — runs only when collected/consumed

Ownership Implications of Iterating

Rust has three distinct ways to iterate, and choosing the wrong one is a genuinely common point of confusion:

MethodYieldsCollection Usable After?
.iter()&T (borrowed references)Yes
.iter_mut()&mut T (mutable references)Yes
.into_iter() / bare for x in vecT (owned values)No — consumed/moved (Ch.3)

Neither Go nor JavaScript has this distinction at all — their iteration never "consumes" the underlying collection the way into_iter() does.

Full circle: Course 1's arc, one more time
Vec<T> and HashMap<K,V> are both heap-allocated — meaning Chapter 3's move semantics apply to them exactly as they did to String. Assigning one Vec to another variable moves it, not copies it; .clone() is still the explicit way to get a real independent duplicate. Every chapter in this course has been building toward being able to read code like this one's filter/map/collect chain and immediately know exactly what's borrowed, what's owned, and what's been moved.
"Type annotations needed" is collect()'s most common complaint
collect() is generic over what it builds — it needs to know the target type from somewhere, either an explicit variable type annotation (let x: Vec<i32> = ...) or the "turbofish" syntax (.collect::<Vec<i32>>()). Omitting both produces a real, common compiler error demanding a type annotation — not a bug in the code's logic, just collect() genuinely being unable to infer what to build without more information.

Coding Challenges

Challenge 1

Create a Vec of five integers. Using an iterator chain (.iter(), .filter(), .map(), .collect()), produce a new Vec containing only the odd numbers, each squared.

📄 View solution
Challenge 2

Create a HashMap mapping three product names (String) to their prices (f64). Look up one product that exists and one that doesn't, handling both cases with match on the Option returned by get.

📄 View solution
Challenge 3

Explain the difference between iterating with .iter(), .iter_mut(), and into_iter() (or a bare for x in vec), specifically in terms of what happens to the original collection afterward in each case.

📄 View solution

Chapter 8 Quick Reference

  • Vec<T> — a growable, heap-backed array; vec![...] macro or Vec::new() + .push()
  • HashMap<K, V> — key-value storage; .get() returns Option<&V>
  • .iter().map(...).filter(...).collect() — a lazy, chainable iterator pipeline, close to JavaScript's array methods
  • .iter() / .iter_mut() — borrow (immutably/mutably); the collection remains usable afterward
  • .into_iter() / bare for x in vec — consumes the collection; it's moved and unusable afterward
  • collect() needs a target type — via variable annotation or the turbofish ::<Type>()
  • Vec and HashMap are heap-allocated — Chapter 3's move semantics apply to both directly

★ Rust Fundamentals Complete — 8 / 8 chapters

From cargo and the toolchain through variables, ownership, borrowing, structs, enums, error handling, and finally collections and iterators — you now have the complete foundation Rust's whole design is built on. Next up: Rust Intermediate, covering lifetimes, traits, generics, smart pointers, concurrency, modules, and testing.