Language Internals
Part 7 of 11 · Rust Language ProficiencyCollections, Iterators & Closures
Vec/HashMap, iterators, closures.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What does Vec own?
Answer
The buffer and its elements. Dropping the Vec drops the elements.
L2
iter, iter_mut, or into_iter?
Answer
iter borrows shared. iter_mut borrows exclusively. into_iter takes ownership and consumes the collection.
L3
When does a chain of map and filter run?
Answer
When a consumer runs, such as collect, sum, or for. Adapters are lazy.
L4
Why the turbofish on collect?
Answer
Many types implement FromIterator. The turbofish, or a let annotation, picks the destination.
L5
Fn, FnMut, or FnOnce?
Answer
Fn can be called through a shared borrow. FnMut needs exclusive access to its captures. FnOnce consumes itself and can run once.
L6
What does a closure capture by default?
Answer
The least powerful borrow that works. A move closure captures by value (Copy types are copied, others are moved), so thread and async tasks own their data.
L7
When do you use the entry API?
Answer
When a HashMap insert and a later mutate would otherwise double-lookup or fight the borrow checker.
Failure modes
collect without a type
Inference fails because more than one collection could be built.
into_iter when a borrow was enough
The caller loses the Vec and cannot use it after the chain.
Mutating through a live iter
The shared iterator borrow overlaps the exclusive mutation.
FnOnce stored and called twice
A closure that moved its capture cannot run again.
Misconceptions
Iterators always allocate.
Adapters are lazy. Allocation happens at collect or when you push into a new Vec yourself.
A closure is a function pointer with no data.
It is an anonymous type that owns or borrows its captures.
HashMap iteration order matches insertion.
The standard HashMap does not promise order. Use a structure that does if order matters.
Interviewer traps
Writing a Python comprehension and assuming the Rust chain owns nothing.
Say whether the iterator borrows or consumes, then what collect allocates.
Using clone inside map to satisfy the checker.
Borrow the item, or into_iter when the old collection is finished.
Design scenario
Same prompt for every reader.
Requirements
iter, not into_iter, a lazy filter and map, and a consumer that does not require a turbofish if sum infers.
Traffic / scale
One Vec per request, tens of thousands of items.
Latency
No intermediate Vec unless the caller needs the filtered list.
Consistency
The original Vec is usable after the sum.
Availability
A borrow error fails the build, not the request.
Failure assumptions
- into_iter consumes the log the handler still needs.
- Each adapter allocates a new list the way a naive chain of arrays would.
Constraints
- Stay on iterators. Do not introduce rayon yet.
Prompt
A request log is a Vec of integers. You need the sum of the even values and you must keep the original Vec.
Borrow the items or consume them
Prefer
Lazy borrow
iter walks shared references. The Vec remains. Work runs at the consumer.
- filter and map build an adapter.
- sum or collect drives it.
- No extra Vec unless you collect.
Alternative
Eager new lists
A chain of array copies or a comprehension that always builds a list.
- Intermediate collections are real.
- The original may still be shared by alias.
- Ownership of the next list is implicit.
Overview
Vec and HashMap own their elements. Three iterators say how you touch them: shared, exclusive, or consuming. Adapters are lazy. Closures capture the surrounding values under the same borrow rules.
Comparative
| Concern | TypeScript | Python | Rust |
|---|---|---|---|
| Growable array | Array | list | Vec |
| Map | Map | dict | HashMap |
| map and filter | methods that allocate | comprehensions | lazy adapters |
| Consume the owner | not a separate mode | not a separate mode | into_iter |
Decisions
- 1
Step 1 Pick the iterator kind
- nextStep 2 What ownership do you need
- ?
Step 2 What ownership do you need
- readStep 3a iter - yields &T
- mutateStep 3b iter_mut - yields &mut T
- consumeStep 3c into_iter - yields T, collection moved
- 3
Step 3a iter - yields &T
- nextStep 4 Chain lazy adapters - filter, map
- 4
Step 3b iter_mut - yields &mut T
- nextStep 4 Chain lazy adapters - filter, map
- 5
Step 3c into_iter - yields T, collection moved
- nextStep 4 Chain lazy adapters - filter, map
- 6
Step 4 Chain lazy adapters - filter, map
- nextStep 5 A consumer runs it - sum, collect, for loop
- no consumer at the endFailure path - nothing runs, must_use warning
- 7
Step 5 A consumer runs it - sum, collect, for loop
- 8
Failure path - nothing runs, must_use warning
Lesson map
Collections, Iterators & Closures
Vec/HashMap, iterators, closures.
Architecture. Step 1 Pick the iterator kind Ready. Step 2 What ownership do you need Ready. Step 3a iter - yields &T Ready. Step 3b iter_mut - yields &mut T Ready. Step 3c into_iter - yields T, collection moved Ready. Step 4 Chain lazy adapters - filter, map Ready. Step 5 A consumer runs it - sum, collect, for loop Ready. Failure path - nothing runs, must_use warning Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB A["Step 1 Pick the iterator kind Ready"] B["Step 2 What ownership do you need Ready"] C["Step 3a iter - yields &T Ready"] D["Step 3b iter_mut - yields &mut T Ready"] E["Step 3c into_iter - yields T, collection moved Ready"] F["Step 4 Chain lazy adapters - filter, map Ready"] G["Step 5 A consumer runs it - sum, collect, for loop Ready"] X["Failure path - nothing runs, must_use warning Ready"] A -->|continues| B B -->|read| C B -->|mutate| D B -->|consume| E C -->|continues| F D -->|continues| F E -->|continues| F F -->|continues| G F -->|no consumer at the end| X
Press Run. Snippets must be self-contained — no network, files, or native modules.
Rosetta — map, filter, sum
fn even_squares(nums: &[i32]) -> i32 {
nums.iter().filter(|n| *n % 2 == 0).map(|n| n * n).sum()
}function evenSquares(nums: number[]): number {
return nums.filter((n) => n % 2 === 0).map((n) => n * n).reduce((a, b) => a + b, 0);
}def even_squares(nums: list[int]) -> int:
return sum(n * n for n in nums if n % 2 == 0)The Rust chain does not build a Vec unless you collect. The TypeScript chain allocates two arrays. The Python generator expression stays lazy until sum.
collect needs a destination when several types implement FromIterator:
let evens: Vec<i32> = nums.iter().copied().filter(|n| n % 2 == 0).collect();The turbofish form is collect::<Vec<_>>().
Rosetta — HashMap, Map, dict
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("ada", 10);
scores.entry("ada").and_modify(|n| *n += 1).or_insert(0);
}const scores = new Map<string, number>();
scores.set("ada", 10);
scores.set("ada", (scores.get("ada") ?? 0) + 1);scores = {"ada": 10}
scores["ada"] = scores.get("ada", 0) + 1The entry API updates in place without a second lookup fighting the borrow. HashMap order is not insertion order.
Rosetta — closures and capture
fn main() {
let factor = 2;
let mul = |n: i32| n * factor;
println!("{}", mul(21));
let owned = String::from("hi");
let once = move || owned;
println!("{}", once());
}const factor = 2;
const mul = (n: number) => n * factor;
console.log(mul(21));factor = 2
mul = lambda n: n * factor
print(mul(21))Fn is a shared call. FnMut mutates captures. FnOnce consumes them. A move closure captures by value (Copy types are copied, others are moved), so thread and async tasks own their data. TypeScript and Python closures alias the outer binding and can keep it alive.
Interview Q&A
Why the turbofish on collect?
Answer
Many types implement FromIterator. The turbofish or a variable type picks which collection you want.
Fn, FnMut, or FnOnce?
Answer
Fn is called through a shared borrow of the closure. FnMut needs exclusive access because it mutates captures. FnOnce runs once because it consumes them.
iter or into_iter if the Vec must survive?
Answer
iter. into_iter consumes the owner.
Do map and filter allocate immediately?
Answer
No. They return adapters. collect, sum, or a for loop runs them.
How is that different from TypeScript map?
Answer
Array map and filter allocate new arrays immediately. The Rust adapters wait.
How is that different from a Python comprehension?
Answer
A generator expression is lazy. A list comprehension allocates. Say which one you meant, then say whether Rust borrowed or consumed.
What does move on a closure do?
Answer
It moves captured values into the closure instead of borrowing them. That is how a thread gets a Send-able closure on a later page.
Which rustc is this iterator model?
Answer
The standard library shipped with rustc 1.98.1. Edition 2024 does not change iter versus into_iter.
Pitfalls
- into_iter when the caller still needs the collection.
- Holding an iterator borrow and then pushing to the same Vec.
- Forgetting the collect type.
- Assuming HashMap order.
- Cloning every item inside map.
You have a Vec of status codes and you need a count of errors. Say which iterator method you call and whether the Vec still exists afterward.