Every way to repeat work over a collection
loop, while, for, iterator adapters and consumers, collect, your own Iterator, and par_iter. Which one reads best for the job.
Ask what the loop is for.
- Repeat until something happens, with no collection involved
(retry until a connection works, read until end of input) →
loopwithbreak, orwhile. - Do something with each item (print it, send it) → a
forloop. Borrow withfor x in &vif you needvafterwards;for x in vconsumes it. - Turn one collection into a value or another collection (sum the
prices, keep the valid lines, build a map) → an iterator chain:
adapters (
map,filter,take) then one consumer (sum,collect,find,fold). - The loop body has early returns, several outputs, or error
handling that fights the chain → go back to a
forloop. Both compile to the same speed; pick the one that reads. - The sequence is computed, not stored (Fibonacci, the lines of a
stream, a tree walk) → your own
Iterator, oriter::from_fn/successorsfor a quick one. - Every item's work is independent and there's a lot of it →
rayon's
par_iter, the same chain spread across cores.
Plain loops
Section
loop, while and for
The three loop forms, and loop as an expression that returns a value.
Section
break, continue and labels
Leave or skip, including out of an outer loop.
Section
for x in v, &v or &mut v
Consume, borrow, or change each element in place.
Iterator chains
Section
The Iterator trait
One method, next, returning Some(item) until None.
Section
Adapters
map, filter, enumerate, zip, take, skip, chain, flat_map.
Section
Consumers
sum, count, find, any, all, fold, min_by_key. Nothing happens until one runs.
Formal. Methods like map, filter, zip and take return a new
iterator and do no work when they are called. Work happens only when
something calls next: a for loop or a finishing method (collect,
sum, count, fold, find, ...). Each call to next pulls one item
through the whole chain, and a finishing method that has its answer
(find, any, or a take that is full) stops pulling. A chain that is
never finished triggers the unused_must_use warning.
In plain English. Writing .map(...) is writing down a recipe, not
cooking. Nothing is cooked until someone asks for a plate, and each plate
goes through every step before the next one starts.
Example. (1..).map(|n| n * n).filter(|n| n % 2 == 1).take(3) is
fine even though 1.. never ends: collect pulls exactly the items
needed for 1, 9 and 25, then stops.
Why this rule exists. Laziness lets a chain skip work it doesn't need, avoid building intermediate collections, and describe infinite sequences, while compiling to the same code as a hand-written loop.
A chain with no consumer does nothing, and the compiler warns you.
Section
Collecting into collections
One collect call builds a Vec, a HashMap, a String, or a Result of a Vec.
Your own sequence
Section
Writing your own iterator
A struct with next, and every adapter comes free.
Example
IntoIterator for &Playlist is what lets a caller write for song in &playlist.The one shape Iterator can't express, and the generic associated types that do.
Choosing, and going faster
Section
Loops or iterators?
Choose by what reads clearly. Measured, they cost the same.
Section
Parallel data processing
The same work split across threads by hand, then with rayon's par_iter.