Skip to content
Select theme

How to iterate over an array

Short answer: for item in array hands you each element by value, for item in &array borrows them, and for item in &mut array gives you a &mut so you can change them in place. Reach for &array unless you actually want the elements moved out.

In src/main.rs:

cargo run prints:

total 255

&scores borrows the array, and the loop variable arrives as an &i32 — one element at a time, borrowed from the array rather than copied out of it. That’s why total += score needs no *: i32 implements AddAssign<&i32>, so adding a borrowed integer to an owned one is a defined operation.

Pass that &i32 somewhere that wants an i32 and the compiler spells the difference out:

cargo build prints:

error[E0308]: mismatched types
--> src/main.rs:9:24
|
9 | total += halve(score);
| ----- ^^^^^ expected `i32`, found `&i32`
| |
| arguments to this function are incorrect
|
note: function defined here
--> src/main.rs:1:4
|
1 | fn halve(n: i32) -> i32 {
| ^^^^^ ------
help: consider dereferencing the borrow
|
9 | total += halve(*score);
| +
For more information about this error, try `rustc --explain E0308`.

The fix is halve(*score), exactly as the help line says. score is a reference to the slot in the array, so *score is the element itself.

If the array arrives as a slice, as in a function taking scores: &[i32], write for score in scores. The & is already in the parameter, and a second one makes &&[i32], which isn’t an iterator.

Drop the & and the loop takes the elements instead of borrowing them:

cargo run prints:

total 255, scores still [72, 88, 95]

scores is still usable afterwards, and that isn’t the loop being polite. for score in scores is an implicit call to into_iter(), which takes the array by value, but [i32; 3] implements Copy, so the array is copied into the loop and the original binding keeps its value.

Change the element type to something that isn’t Copy and the same line stops working:

cargo build prints:

error[E0382]: borrow of moved value: `words`
--> src/main.rs:7:38
|
2 | let words = [String::from("alpha"), String::from("beta")];
| ----- move occurs because `words` has type `[String; 2]`, which does not implement the `Copy` trait
3 | let mut chars = 0;
4 | for word in words {
| ----- `words` moved due to this implicit call to `.into_iter()`
...
7 | println!("{chars} characters in {words:?}");
| ^^^^^ value borrowed here after move
|
note: the `for` loop is desugared into a call to `std::iter::IntoIterator::into_iter`, which takes ownership of the receiver `self`, which moves `words`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/collect.rs:312:17
help: consider iterating over a slice of the `[String; 2]`'s content to avoid moving into the `for` loop
|
4 | for word in &words {
| +
For more information about this error, try `rustc --explain E0382`.

words is gone by the last line. The compiler names the mechanism: for desugars into into_iter(), and that takes ownership of the receiver. Put the & back with for word in &words, and each element arrives as a &String with the array still intact at the end.

&mut array is the third form, and it’s the one that writes back:

cargo run prints:

[77, 93, 100]

score is a &mut i32, so *score += 5 assigns through the reference and the array changes in place. Two muts are doing different jobs: the one on let mut scores is what allows the array to be borrowed mutably at all, and the one in &mut scores is what the loop needs.

&scores and scores.iter() are the same thing, and so are &mut scores and scores.iter_mut(); the operator forms are sugar over the methods. The third method, scores.into_iter(), is the by-value one: in edition 2024 it hands you the elements themselves rather than references to them.