Visiting every entry
A HashMap stores pairs: a key and the value that goes with it. A for loop over the map visits one pair per turn, and the loop variable can be split into the key and the value right in the loop header:
let stock = HashMap::from([("apples", 5), ("pears", 2)]);
for (fruit, amount) in &stock {
println!("{} {}", fruit, amount);
}
This prints pears 2 and apples 5 in one run. Another run may print them the other way round. The key always comes first in the pair, then the value.
(In a file of your own, add use std::collections::HashMap; at the top. The lesson's editor already has it.)
Why the & matters
Writing &stock borrows the map for the loop. Without the &, the loop takes the map for itself and uses it up. After such a loop, stock is gone, and using it is a compile error:
error[E0382]: borrow of moved value: `stock`
...
4 | for (fruit, amount) in stock {
| ----- `stock` moved due to this implicit call to `.into_iter()`
...
7 | println!("{}", stock.len());
| ^^^^^ value borrowed here after move
The compiler's own suggestion is the fix: loop over &stock.
Keys and values come as references
When you loop over &stock, both parts of each pair are references into the map, not copies. To use the number itself, write *amount. Comparing a reference with a plain number does not compile:
error[E0308]: mismatched types
11 | if points >= 10 { t2 += 1; }
| ^^ expected `&_`, found integer
help: consider dereferencing the borrow
11 | if *points >= 10 { t2 += 1; }
Arithmetic such as total += points also happens to work on a reference, but writing *points everywhere is clearer and always works.
Only the keys, only the values
You don't have to unpack both parts. Put _ where you don't need one, as in for (_, amount) in &stock. Or ask the map for just one side:
let mut s = 0;
for amount in stock.values() {
s += amount;
}
println!("{}", s);
let mut keys: Vec<&&str> = stock.keys().collect();
keys.sort();
println!("{:?}", keys);
These print 7 and ["apples", "pears"]. The keys here are &str values and keys() hands out references to them, hence the double & in the type.
Changing values while you loop
To change the values, borrow the map mutably with &mut. Each value then arrives as a &mut reference, and *amount += 1 changes the entry in the map itself. On a copy of stock this turns the entries into ("apples", 6) and ("pears", 3). The map must be declared with let mut for this to compile.
The order is not fixed
A HashMap has no fixed order, and Rust may even change it from one run to the next. So loop over a map when the answer doesn't depend on the order: a total, a count, a check. When you need a stable order, collect the pairs into a vector and sort it:
let mut fruits: Vec<_> = stock.iter().collect();
fruits.sort();
for (fruit, amount) in fruits {
println!("{}: {}", fruit, amount);
}
This always prints apples: 5, then pears: 2.
The lesson's task
The map scores holds each player's points. The task is to add all points to total and count the players with 10 points or more in count. Only the points matter, so the key is skipped with _:
let scores = HashMap::from([("Ana", 12), ("Ben", 7), ("Cleo", 15), ("Dan", 9)]);
let mut total = 0;
let mut count = 0;
for (_, points) in &scores {
total += *points;
if *points >= 10 {
count += 1;
}
}
println!("{}", total);
println!("{}", count);
The output is 43 and 2: 12 + 7 + 15 + 9 is 43, and only Ana and Cleo have 10 or more. A total and a count come out the same whatever order the players are visited in.
Key takeaways
for (key, value) in &mapvisits every entry once and leaves the map usable afterwards.- Without the
&, the loop moves the map away. - Both parts are references: use
*valueto get the number. - Use
_for a part you don't need, orkeys()andvalues()for one side. - The order is not fixed, so sort first when it matters.
