Skip to content
Learn Rust
Theme

Looping Over a Collection

Working with collections · Lesson 29

Rust has three ways to write for x in … over a list, and they differ in who owns the items afterwards. One borrows the list, one borrows it to change it, and one uses it up.

Coming from Gofor n in &names is for _, n := range names, and v.iter().enumerate() gives i, n. In Go n is a copy of the item, so you change items through the index. In Rust for n in &mut nums gives you a &mut reference to the item in the list (*n *= 10 writes through it). for n in names (no &) has no Go counterpart: for a Vec it moves the items out, and names is gone.

Coming from Kotlinfor n in &names is for (n in names), and v.iter().enumerate() is withIndex(). The loop variable in Kotlin is a val you cannot assign to; in Rust for n in &mut nums gives you a &mut reference to change the item through. Leave off the & and the loop takes ownership of a Vec, so you can't use it afterwards.

Coming from Pythonfor n in &names is Python's for n in names, and v.iter().enumerate() is enumerate(names). In Python n *= 10 only rebinds the name n; in Rust for n in &mut nums gives you a &mut reference to the item (*n *= 10 writes through it). Leaving off the & moves the list into the loop, and names can no longer be used.

Coming from TypeScriptfor n in &names is for (const n of names), and v.iter().enumerate() is names.entries(). In for...of a number n is a copy, so you assign through the index; in Rust for n in &mut nums gives you a &mut reference to the item (*n *= 10 writes through it). Without the & the loop moves the list in, and it can't be used afterwards.

Coming from Javafor n in &names is your for-each: it reads each item and leaves the list alone. for n in names (no &) consumes the list, which a for-each never does. For numbers a Java loop variable is a copy, so you call set(i, …); in Rust for n in &mut nums gives you a &mut reference to the item. Changing the objects in a list (p.age++) needs that &mut form too: &names is read-only.

Coming from C#for n in &names is foreach, and for n in &mut nums changes items in place, where foreach does not let you assign to the loop variable. Writing for n in names without & uses a Vec up, so names is gone after the loop.

Coming from CInstead of an index loop, for n in &nums reads each item and for n in &mut nums hands you a &mut reference to change it in place. The length travels with the list, so there is no separate count to get wrong. Without the & the loop takes ownership of a Vec, and it can't be used afterwards.

Coming from C++for n in &v is for (const auto& n : v), and for n in &mut v is for (auto& n : v). for n in v is the odd one: for a Vec it moves the items out and consumes v, rather than copying each one like for (auto n : v).

The quick comparison

Rustsrc/main.rsRun ↗
fn main() {
let names = vec![String::from("ana"), String::from("bo")];
// Borrow each item: names stays usable.
for n in &names {
println!("hi {n}");
}
let mut nums = vec![1, 2, 3];
// Borrow each item so it can be changed.
for n in &mut nums {
*n *= 10;
}
// Each item with its position.
for (i, n) in nums.iter().enumerate() {
println!("{i}: {n}");
}
println!("{} names, {nums:?}", names.len());
}
$ cargo run
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]
package main
import "fmt"
func main() {
names := []string{"ana", "bo"}
for _, n := range names {
fmt.Println("hi", n)
}
nums := []int{1, 2, 3}
for i := range nums {
nums[i] *= 10
}
for i, n := range nums {
fmt.Printf("%d: %d\n", i, n)
}
fmt.Println(len(names), "names,", nums)
}
$ go run main.go
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10 20 30]
fun main() {
val names = listOf("ana", "bo")
for (n in names) {
println("hi $n")
}
val nums = mutableListOf(1, 2, 3)
for (i in nums.indices) {
nums[i] *= 10
}
for ((i, n) in nums.withIndex()) {
println("$i: $n")
}
println("${names.size} names, $nums")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]
names = ["ana", "bo"]
for n in names:
print(f"hi {n}")
nums = [1, 2, 3]
for i in range(len(nums)): # "n *= 10" would only rebind n, so assign through the index
nums[i] *= 10
for i, n in enumerate(nums):
print(f"{i}: {n}")
print(f"{len(names)} names, {nums}")
$ python3 main.py
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]
const names = ["ana", "bo"];
for (const n of names) {
console.log(`hi ${n}`);
}
const nums = [1, 2, 3];
for (let i = 0; i < nums.length; i++) { // n holds a copy of each number, so assign through the index
nums[i] *= 10;
}
for (const [i, n] of nums.entries()) { // with the position
console.log(`${i}: ${n}`);
}
console.log(`${names.length} names,`, nums);
$ tsc --strict --module nodenext --types node main.ts && node main.js
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [ 10, 20, 30 ]
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
var names = List.of("ana", "bo");
for (var n : names) {
System.out.println("hi " + n);
}
var nums = new ArrayList<>(List.of(1, 2, 3));
for (int i = 0; i < nums.size(); i++) { // change items by index
nums.set(i, nums.get(i) * 10);
}
for (int i = 0; i < nums.size(); i++) { // with index
System.out.println(i + ": " + nums.get(i));
}
System.out.println(names.size() + " names, " + nums);
}
}
$ javac Main.java && java Main
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]
List<string> names = ["ana", "bo"];
foreach (var n in names)
{
Console.WriteLine($"hi {n}");
}
List<int> nums = [1, 2, 3];
for (var i = 0; i < nums.Count; i++) // foreach can't change items, so use an index
{
nums[i] *= 10;
}
foreach (var (n, i) in nums.Select((n, i) => (n, i))) // with index
{
Console.WriteLine($"{i}: {n}");
}
Console.WriteLine($"{names.Count} names, [{string.Join(", ", nums)}]");
$ dotnet run
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]
#include <stdio.h>
int main(void) {
const char *names[] = {"ana", "bo"};
size_t names_len = sizeof names / sizeof names[0];
for (size_t i = 0; i < names_len; i++) {
printf("hi %s\n", names[i]);
}
int nums[] = {1, 2, 3};
for (size_t i = 0; i < 3; i++) {
nums[i] *= 10; // change each item through its index
}
for (size_t i = 0; i < 3; i++) {
printf("%zu: %d\n", i, nums[i]);
}
printf("%zu names, [%d, %d, %d]\n", names_len, nums[0], nums[1], nums[2]);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> names{"ana", "bo"};
for (const auto& n : names) { // const reference: no copy, no change
std::cout << "hi " << n << "\n";
}
std::vector<int> nums{1, 2, 3};
for (auto& n : nums) { // reference: change each item in place
n *= 10;
}
for (std::size_t i = 0; i < nums.size(); ++i) { // with an index
std::cout << i << ": " << nums[i] << "\n";
}
std::cout << names.size() << " names, [";
for (std::size_t i = 0; i < nums.size(); ++i) {
std::cout << (i ? ", " : "") << nums[i];
}
std::cout << "]\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
hi ana
hi bo
0: 10
1: 20
2: 30
2 names, [10, 20, 30]

What's different

  • for x in v takes each item out of v; a Vec is used up (an array of numbers is copied, so it stays). for x in &v borrows each item; v stays. for x in &mut v borrows each item so you can change it.
  • &v is the same as v.iter(), and &mut v is the same as v.iter_mut(). With the borrowing forms x is a reference, so you may need *x to get the value.
  • Use the first form when you are done with the list and want to move its items somewhere else without copying them.
  • Do not add to or remove from a list inside a for over it: Rust refuses to compile it, and retain or a list of positions are the usual fixes.
Gotcha

Looping, then using the list again

Looks fine: in Go, Kotlin, Python, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.

Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.Works in C. In Rust it's just a rule.Works in C++. In Rust it's just a rule.
Rustsrc/main.rs✗ won't compile
fn main() {
let names = vec![String::from("ana"), String::from("bo")];
for n in names {
println!("hi {n}");
}
println!("{} names", names.len());
}
package main
import "fmt"
func main() {
names := []string{"ana", "bo"}
for _, n := range names {
fmt.Println("hi", n)
}
fmt.Println(len(names), "names")
}
$ go run main.go✓ runs
hi ana
hi bo
2 names
fun main() {
val names = listOf("ana", "bo")
for (n in names) {
println("hi $n")
}
println("${names.size} names")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
hi ana
hi bo
2 names
names = ["ana", "bo"]
for n in names:
print(f"hi {n}")
print(f"{len(names)} names")
$ python3 main.py✓ runs
hi ana
hi bo
2 names
const names = ["ana", "bo"];
for (const n of names) {
console.log(`hi ${n}`);
}
console.log(`${names.length} names`);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
hi ana
hi bo
2 names
import java.util.List;
public class Main {
public static void main(String[] args) {
var names = List.of("ana", "bo");
for (var n : names) {
System.out.println("hi " + n);
}
System.out.println(names.size() + " names");
}
}
$ javac Main.java && java Main✓ runs
hi ana
hi bo
2 names
List<string> names = ["ana", "bo"];
foreach (var n in names)
{
Console.WriteLine($"hi {n}");
}
Console.WriteLine($"{names.Count} names");
$ dotnet run✓ runs
hi ana
hi bo
2 names
#include <stdio.h>
int main(void) {
const char *names[] = {"ana", "bo"};
size_t len = sizeof names / sizeof names[0];
for (size_t i = 0; i < len; i++) {
printf("hi %s\n", names[i]);
}
printf("%zu names\n", len);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
hi ana
hi bo
2 names
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> names{"ana", "bo"};
for (auto n : names) {
std::cout << "hi " << n << "\n";
}
std::cout << names.size() << " names\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
hi ana
hi bo
2 names
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0382?
error[E0382]: borrow of moved value: `names`
 --> src/main.rs:6:26
  |
2 |     let names = vec![String::from("ana"), String::from("bo")];
  |         ----- move occurs because `names` has type `Vec<String>`, which does not implement the `Copy` trait
3 |     for n in names {
  |              ----- `names` moved due to this implicit call to `.into_iter()`
...
6 |     println!("{} names", names.len());
  |                          ^^^^^ value borrowed here after move
  |
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
  |
3 |     for n in &names {
  |              +

  ... (notes pointing into the standard library trimmed)
What happens in Go

Go runs this and prints both names, then 2 names. range leaves the slice alone, so it is still there afterwards.

What happens in Kotlin

Kotlin runs this and prints both names, then 2 names. A for loop does not take the list away, so it is still there afterwards.

What happens in Python

Python prints both names and then 2 names. A for loop does not take the list away, so it is still there afterwards.

What happens in TypeScript

Prints both names, then 2 names. for...of does not take the array away, so it is still there afterwards.

What happens in Java

A for-each loop in Java does not take the list away, so names is still there afterwards. Rust's for n in names uses the list up instead.

What happens in C#

foreach does not take the list away, so names is still there afterwards and this prints 2 names.

What happens in C

A C index loop reads the array and nothing about it makes names unusable, so the length is still 2 afterwards.

What happens in C++

for (auto n : names) copies each string, so names is untouched and still has 2 items. Rust's for n in names moves them out instead.

What Rust sees

for n in names (no &) hands the whole list to the loop. Each pass moves one String out into n, and the list's buffer is freed when the loop ends, so names cannot be used afterwards.

Why Rust says no

This is the same move as in the Ownership lesson, hidden inside a loop. Rust makes you choose: add one & and the loop borrows, or leave it off to give the list away.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let names = vec![String::from("ana"), String::from("bo")];
for n in &names {
println!("hi {n}");
}
println!("{} names", names.len());
}
$ cargo run
hi ana
hi bo
2 names

The toolkit

Outputs on this page come from Linux on 64-bit ARM with Rust 1.99.1.

Three forms of the loop

A for loop takes anything that can hand out items one at a time (an iterator). For a Vec you pick one of three forms. The choice decides what x is and who owns the list afterwards:

Rustsrc/main.rsRun ↗
use std::any::type_name_of_val;
fn main() {
let mut nums = vec![10, 20];
for x in &nums {
println!("for x in &nums: x is {} ({x})", type_name_of_val(&x));
}
for x in &mut nums {
*x += 1;
println!("for x in &mut nums: x is {} ({x})", type_name_of_val(&x));
}
for x in nums {
println!("for x in nums: x is {} ({x})", type_name_of_val(&x));
}
}
$ cargo run
for x in &nums: x is &i32 (10)
for x in &nums: x is &i32 (20)
for x in &mut nums: x is &mut i32 (11)
for x in &mut nums: x is &mut i32 (21)
for x in nums: x is i32 (11)
for x in nums: x is i32 (21)
You write x is The list afterwards Same as
for x in &v &T, a read-only reference unchanged v.iter()
for x in &mut v &mut T, a reference you can write through (&mut) still yours; items may have changed v.iter_mut()
for x in v T, the item itself moved into the loop: unusable after it (unless v is an array of Copy items) v.into_iter()

iter(), iter_mut() and into_iter() are the methods that hand out the items; for calls them when you write &v, &mut v or v (the end of the page shows it).

Use the borrowing forms unless you are done with the list. The third form is the gotcha at the top of the page: for a Vec<String> it moves each String out, and the compiler refuses a later use of v. An array has the same three forms. An array of numbers is copied into the by-value loop and stays usable, while an array of Strings is moved like a Vec. A slice (&[T]) is already a reference, so for x in s borrows and x is &T. A range written directly (for i in 0..3) gives the numbers themselves. How other languages share or copy items in a loop is compared in Copy or reference.

Getting the value out of &T

With &v and &mut v, x is a reference. *x follows it to the value, the pattern &x after for does that once per item (Patterns explains it), and arithmetic like x + 1 works on a &i32 directly:

Rustsrc/main.rsRun ↗
fn main() {
let nums = vec![10, 20, 30];
let mut total = 0;
for x in &nums {
total += *x; // `*x` follows the reference to the i32
}
println!("with *x: {total}");
let mut total = 0;
for &x in &nums {
total += x; // the pattern `&x` does the same, once per item
}
println!("with &x: {total}");
for x in &nums {
println!("x + 1: {}", x + 1); // arithmetic works on a &i32 directly
}
}
$ cargo run
with *x: 60
with &x: 60
x + 1: 11
x + 1: 21
x + 1: 31

The &x pattern copies the item out, so it needs an item that can be copied. A String cannot, which is the E0507 error that Patterns shows. Use the reference as it is (n.len(), println!("{n}")), or ask for a copy with n.clone().

Position and backwards

Rustsrc/main.rsRun ↗
fn main() {
let names = vec!["ana", "bo", "cy"];
for (i, n) in names.iter().enumerate() {
println!("enumerate: {i} is {n}");
}
for n in names.iter().rev() {
println!("rev: {n}");
}
for n in &names[1..] {
println!("slice [1..]: {n}");
}
let mut scores = vec![5, 5, 5];
for (i, s) in scores.iter_mut().enumerate() {
*s += i * 10;
}
println!("iter_mut().enumerate(): {scores:?}");
}
$ cargo run
enumerate: 0 is ana
enumerate: 1 is bo
enumerate: 2 is cy
rev: cy
rev: bo
rev: ana
slice [1..]: bo
slice [1..]: cy
iter_mut().enumerate(): [5, 15, 25]
To loop… Write x is
with the position for (i, x) in v.iter().enumerate() (usize, &T)
backwards for x in v.iter().rev() &T
over part of the list for x in &v[1..3] &T
with the original position, backwards for (i, x) in v.iter().enumerate().rev() (usize, &T)
with the position, changing the item for (i, x) in v.iter_mut().enumerate() (usize, &mut T)

enumerate and rev are adaptors: methods that turn one iterator into another. Their order changes the numbers:

Rustsrc/main.rsRun ↗
fn main() {
let names = ["ana", "bo", "cy"];
for (i, n) in names.iter().enumerate().rev() {
println!("enumerate().rev(): {i} is {n}");
}
for (i, n) in names.iter().rev().enumerate() {
println!("rev().enumerate(): {i} is {n}");
}
}
$ cargo run
enumerate().rev(): 2 is cy
enumerate().rev(): 1 is bo
enumerate().rev(): 0 is ana
rev().enumerate(): 0 is cy
rev().enumerate(): 1 is bo
rev().enumerate(): 2 is ana

enumerate().rev() attaches each position first, so walking backwards still prints the original indices, 2 down to 0. rev().enumerate() reverses first and counts what it is handed, so cy is numbered 0. The first order works because a slice iterator knows its length and can run from the back, and enumerate passes that on.

zip, step_by, skip and the rest are in Iterators, and so are the slice methods windows and chunks.

Loops over positions

for i in 0..v.len() is the loop you know from C, Go and Java, and it works. It reads v[i], a bounds-checked index, and nothing ties i to the list, so a slip such as ..= for .. is a panic:

Rustsrc/main.rs! panics at runtimeRun ↗
fn main() {
let v = vec![10, 20, 30];
for i in 0..=v.len() {
println!("v[{i}] = {}", v[i]);
}
}
$ cargo run
v[0] = 10
v[1] = 20
v[2] = 30

thread 'main' (1) panicked at src/main.rs:4:34:
index out of bounds: the len is 3 but the index is 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

for x in &v has no index to get wrong. An empty list is the other trap: v.len() - 1 underflows, because usize cannot go below zero (Loops runs it).

An index is the right tool when the body needs a neighbour as well as the item:

Rustsrc/main.rsRun ↗
fn main() {
// Each total needs the one before it, so an index is the natural tool.
let mut totals = vec![3, 1, 4, 1, 5];
for i in 1..totals.len() {
totals[i] += totals[i - 1];
}
println!("running totals: {totals:?}");
// On an empty list, 1..0 is an empty range: nothing runs, nothing panics.
let mut empty: Vec<i32> = vec![];
for i in 1..empty.len() {
empty[i] += empty[i - 1];
}
println!("empty: {empty:?}");
}
$ cargo run
running totals: [3, 4, 8, 9, 14]
empty: []

On an empty list 1..empty.len() is the empty range 1..0, so the body never runs.

Changing a list while looping over it

for x in &v borrows v until the loop ends, and remove needs &mut v. The compiler refuses:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut v = vec![1, 2, 3, 4];
for x in &v {
if x % 2 == 0 {
v.remove(0);
}
}
println!("v: {v:?}");
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:13
  |
3 |       for x in &v {
  |       -        --
  |       |        |
  |       |        immutable borrow occurs here
  |  _____|        immutable borrow later used here
  | |
4 | |         if x % 2 == 0 {
5 | |             v.remove(0);
  | |             ^^^^^^^^^^^ mutable borrow occurs here
6 | |         }
7 | |     }
  | |_____- this for loop borrows `v` immutably, preventing mutation within its body
  |
  = help: consider using an index-based loop instead, or collecting modifications into a separate collection

The compiler’s own help line names two of the fixes: “consider using an index-based loop instead, or collecting modifications into a separate collection”. Mutable borrows explains the rule and runs the same mistake in other languages. Here are the ways out:

Rustsrc/main.rsRun ↗
fn main() {
// 1. Remove items: retain keeps the ones for which the closure says true.
let mut v = vec![1, 2, 3, 4, 5, 6];
v.retain(|x| x % 2 != 0);
println!("retain: {v:?}");
// 2. Collect what to change first; the loop over `&v` has ended before the change.
let mut v = vec![1, 2, 3, 4, 5, 6];
let mut doomed = Vec::new();
for (i, x) in v.iter().enumerate() {
if x % 2 == 0 {
doomed.push(i);
}
}
for i in doomed.into_iter().rev() {
v.remove(i);
}
println!("collect, then apply: {v:?}");
// 3. Loop over positions. The range is made once, so no borrow of `v` lasts across the body.
let mut v = vec![1, 2, 3, 4, 5, 6];
for i in (0..v.len()).rev() {
if v[i] % 2 == 0 {
v.remove(i);
}
}
println!("positions, backwards: {v:?}");
// Changing the items is not a problem: `&mut` is the one writer.
let mut v = vec![1, 2, 3, 4, 5, 6];
for x in &mut v {
if *x % 2 == 0 {
*x = 0;
}
}
println!("&mut: {v:?}");
}
$ cargo run
retain: [1, 3, 5]
collect, then apply: [1, 3, 5]
positions, backwards: [1, 3, 5]
&mut: [1, 0, 3, 0, 5, 0]
You want to Use
remove items v.retain(|x| keep(x)): |x| … is a closure, a small function written in place (Closures) that says which items stay
change every item for x in &mut v: one writer, no other borrow
add or remove depending on what you saw collect the positions or new items in one loop, change v in a second
remove while walking loop over (0..v.len()).rev(): the range is made once, and removing item i only moves later items
move items into a new list for x in v and push each one where it belongs (next section; the last exercise)

Taking the items out

Sometimes you want the items themselves, as owned values: to push them into another list, send them somewhere or finish with them. for x in v does that for the whole list. Two tools take less than all of it, or work on a list you only have a &mut to:

Rustsrc/main.rsRun ↗
// `for msg in outbox` would give `&mut String`, not owned strings.
// take moves the whole list out and leaves an empty one in its place.
fn send_all(outbox: &mut Vec<String>) {
for msg in std::mem::take(outbox) {
println!("sent {msg}");
}
}
fn main() {
let mut names = vec![String::from("ana"), String::from("bo"), String::from("cy")];
// drain(..2) moves the first two items out and leaves the rest in the list.
for n in names.drain(..2) {
println!("drained {n}");
}
println!("left in the list: {names:?}");
send_all(&mut names);
println!("names after send_all: {names:?}");
}
$ cargo run
drained ana
drained bo
left in the list: ["cy"]
sent cy
names after send_all: []
  • v.drain(range) removes those items and hands them to the loop. The rest stay in v, which is still yours. drain(..) takes everything, and works on a &mut Vec parameter too.
  • std::mem::take(&mut v) moves the whole list out and leaves an empty one in its place (Mutable borrows covers take). It is one way to take the items of a &mut Vec parameter, where for x in v would give you &mut items, not owned ones.

Maps and text

Rustsrc/main.rsRun ↗
use std::any::type_name_of_val;
use std::collections::HashMap;
fn main() {
let mut stock = HashMap::new();
stock.insert("apple", 3);
stock.insert("pear", 0);
for pair in &stock {
println!("for pair in &stock: pair is {}", type_name_of_val(&pair));
break;
}
for (_, count) in &mut stock {
*count += 10; // values can be changed; keys cannot
}
let mut totals: Vec<_> = stock.values().collect();
totals.sort();
println!("counts after +10: {totals:?}");
for (i, c) in "héllo".char_indices() {
print!("{i}:{c} ");
}
println!();
}
$ cargo run
for pair in &stock: pair is (&&str, &i32)
counts after +10: [10, 13]
0:h 1:é 3:l 4:l 5:o 

A HashMap loops in pairs: for (k, v) in &map gives (&K, &V), and &mut map gives (&K, &mut V). You may change values, not keys. The keys here are &str, so a reference to one prints as &&str. The order is not yours to rely on (Maps). A String or &str is not looped directly: pick chars(), bytes() or char_indices(), as the last line does (Strings).

Your loops in Rust

Going deeper

What the three forms call

for calls IntoIterator::into_iter on what follows in, then next() until it gets None (Loops and Iterators explain it). Vec<T>, &Vec<T> and &mut Vec<T> each have their own into_iter, which is why one for has three forms:

Rustsrc/main.rsRun ↗
use std::any::type_name_of_val;
fn main() {
let mut v = vec![1, 2, 3];
println!(
"(&v).into_iter(): {}",
type_name_of_val(&(&v).into_iter())
);
println!("v.iter(): {}", type_name_of_val(&v.iter()));
println!(
"(&mut v).into_iter(): {}",
type_name_of_val(&(&mut v).into_iter())
);
println!("v.iter_mut(): {}", type_name_of_val(&v.iter_mut()));
// clone() so that v can be used again below
println!(
"v.clone().into_iter(): {}",
type_name_of_val(&v.clone().into_iter())
);
let a: Vec<&i32> = (&v).into_iter().collect();
let b: Vec<&i32> = v.iter().collect();
println!("&v and v.iter() give the same items: {}", a == b);
}
$ cargo run
(&v).into_iter():     core::slice::iter::Iter<'_, i32>
v.iter():             core::slice::iter::Iter<'_, i32>
(&mut v).into_iter(): core::slice::iter::IterMut<'_, i32>
v.iter_mut():         core::slice::iter::IterMut<'_, i32>
v.clone().into_iter(): alloc::vec::into_iter::IntoIter<i32>
&v and v.iter() give the same items: true

&v becomes the same iterator as v.iter(), and &mut v the same as v.iter_mut(). The consuming form is a third type that owns the list’s buffer. The names are the compiler’s; you will mostly meet them in error messages.

A consuming loop that stops early

What happens to the items a consuming loop never reached? A type can run code when a value is dropped, which means it goes away (that is the Drop trait; Ownership shows the order). Here it only prints:

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" dropped {}", self.0);
}
}
fn main() {
let items = vec![Noisy("a"), Noisy("b"), Noisy("c"), Noisy("d")];
println!("loop starts");
for item in items {
println!(" got {}", item.0);
if item.0 == "b" {
println!(" break");
break;
}
}
println!("loop is over");
}
$ cargo run
loop starts
  got a
  dropped a
  got b
  break
  dropped b
  dropped c
  dropped d
loop is over

Each item is dropped at the end of its pass. After the break, c and d were never visited. items has been moved, so they sit in the buffer the loop’s hidden iterator owns, and it drops them when the loop ends.

for item in items { … break at b }
STACKHEAPitems: Vec<Noisy> (moved)ptrlen 4cap 4item: Noisybdropped when this pass ends(the loop): vec::IntoIter<Noisy>owns the buffernext: cnot a name you can useitems' buffer: 4 × Noisya: goneb: in itemcd
The moment of the break. When the loop ends, c and d are dropped, then the buffer is freed. The iterator's fields are drawn in a convenient order; the standard library does not promise one.

A borrowing loop owns nothing, so a break drops nothing:

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" dropped {}", self.0);
}
}
fn main() {
let items = vec![Noisy("a"), Noisy("b"), Noisy("c")];
for item in &items {
println!(" saw {}", item.0);
if item.0 == "b" {
break;
}
}
println!("loop is over, items still here: {}", items.len());
}
$ cargo run
  saw a
  saw b
loop is over, items still here: 3
  dropped a
  dropped b
  dropped c

Nothing was dropped until items itself went out of scope at the end of main. drain(..) is a consuming loop over a range, and it removes the whole range even if you break:

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" dropped {}", self.0);
}
}
fn main() {
let mut items = vec![Noisy("a"), Noisy("b"), Noisy("c")];
for item in items.drain(..) {
println!(" got {}", item.0);
if item.0 == "b" {
break;
}
}
println!("loop is over, items left: {}", items.len());
}
$ cargo run
  got a
  dropped a
  got b
  dropped b
  dropped c
loop is over, items left: 0

c was dropped by the drain, and items is empty. If you want to keep the unconsumed items, borrow, or drain only the part you want.

Practice

Try it

Each exercise has checks in main. Open it in the Playground, replace the todo!(), and run it until it prints all checks passed.

Tidy names in place

Write tidy: trim the spaces around every name and make it lowercase, changing the list itself. Keep the empty list, an all-space name and non-English letters in mind. A loop over a clone of the list leaves the original untouched, and the checks catch that.

Starterexercises/tidy.rs
// Trim the spaces around each name and make it lowercase, changing the list in place.
fn tidy(names: &mut Vec<String>) {
todo!("loop over `&mut` items, or `names.iter_mut()`, and assign `*n = ...`")
}
fn main() {
let mut names = vec![
String::from(" Ana "),
String::from("BO"),
String::from(" ÉLAN"),
];
tidy(&mut names);
assert_eq!(names, ["ana", "bo", "élan"]);
let mut empty: Vec<String> = Vec::new();
tidy(&mut empty);
assert!(empty.is_empty());
let mut blanks = vec![String::from(" "), String::from("x")];
tidy(&mut blanks);
assert_eq!(blanks, ["", "x"]);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/tidy.solution.rsRun ↗
// Trim the spaces around each name and make it lowercase, changing the list in place.
fn tidy(names: &mut Vec<String>) {
for n in names.iter_mut() {
*n = n.trim().to_lowercase();
}
}
fn main() {
let mut names = vec![
String::from(" Ana "),
String::from("BO"),
String::from(" ÉLAN"),
];
tidy(&mut names);
assert_eq!(names, ["ana", "bo", "élan"]);
let mut empty: Vec<String> = Vec::new();
tidy(&mut empty);
assert!(empty.is_empty());
let mut blanks = vec![String::from(" "), String::from("x")];
tidy(&mut blanks);
assert_eq!(blanks, ["", "x"]);
println!("all checks passed");
}
$ cargo run
all checks passed

Number the lines

Write number: put 1: in front of the first line, 2: in front of the second, and so on, changing the list itself. Counting starts at 1, and the checks include a list with more than nine lines.

Starterexercises/number.rs
// Put a number in front of every line, starting at 1, changing the list in place:
// ["a", "b"] becomes ["1: a", "2: b"].
fn number(lines: &mut Vec<String>) {
todo!("walk the lines together with their positions, and write each one back")
}
fn main() {
let mut lines = vec![String::from("a"), String::from("b"), String::from("c")];
number(&mut lines);
assert_eq!(lines, ["1: a", "2: b", "3: c"]);
let mut empty: Vec<String> = Vec::new();
number(&mut empty);
assert!(empty.is_empty());
let mut blank = vec![String::new(), String::from("x")];
number(&mut blank);
assert_eq!(blank, ["1: ", "2: x"]);
let mut many: Vec<String> = (0..12).map(|_| String::from("z")).collect();
number(&mut many);
assert_eq!(many[9], "10: z");
assert_eq!(many[11], "12: z");
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/number.solution.rsRun ↗
// Put a number in front of every line, starting at 1, changing the list in place:
// ["a", "b"] becomes ["1: a", "2: b"].
fn number(lines: &mut Vec<String>) {
for (i, line) in lines.iter_mut().enumerate() {
*line = format!("{}: {line}", i + 1);
}
}
fn main() {
let mut lines = vec![String::from("a"), String::from("b"), String::from("c")];
number(&mut lines);
assert_eq!(lines, ["1: a", "2: b", "3: c"]);
let mut empty: Vec<String> = Vec::new();
number(&mut empty);
assert!(empty.is_empty());
let mut blank = vec![String::new(), String::from("x")];
number(&mut blank);
assert_eq!(blank, ["1: ", "2: x"]);
let mut many: Vec<String> = (0..12).map(|_| String::from("z")).collect();
number(&mut many);
assert_eq!(many[9], "10: z");
assert_eq!(many[11], "12: z");
println!("all checks passed");
}
$ cargo run
all checks passed

Move the long names out

Write take_long: remove every Job whose name is longer than max bytes from the list and return those jobs, both lists keeping their order. A Job cannot be cloned, so whatever you write has to move the jobs. retain would drop the ones it removes, and for j in jobs on a &mut Vec hands out &mut Job; the section on taking items out has the tools. A loop that moves the jobs into two lists works.

Starterexercises/take-long.rs
// A job cannot be cloned or copied: it can only be moved.
struct Job {
name: String,
}
fn job(name: &str) -> Job {
Job {
name: name.to_string(),
}
}
// Remove every job whose name is longer than `max` bytes and return those jobs,
// in their original order. The jobs left in the list keep their order too.
fn take_long(jobs: &mut Vec<Job>, max: usize) -> Vec<Job> {
todo!("move the jobs out of the list, then put the short ones back")
}
fn names(jobs: &[Job]) -> Vec<&str> {
jobs.iter().map(|j| j.name.as_str()).collect()
}
fn main() {
let mut jobs = vec![job("a"), job("backup"), job("cd"), job("deploy")];
let long = take_long(&mut jobs, 2);
assert_eq!(names(&jobs), ["a", "cd"]);
assert_eq!(names(&long), ["backup", "deploy"]);
let mut none: Vec<Job> = Vec::new();
assert!(take_long(&mut none, 3).is_empty());
assert!(none.is_empty());
let mut all_short = vec![job("x"), job("y")];
assert!(take_long(&mut all_short, 5).is_empty());
assert_eq!(names(&all_short), ["x", "y"]);
let mut all_long = vec![job("alpha"), job("beta")];
let taken = take_long(&mut all_long, 1);
assert!(all_long.is_empty());
assert_eq!(names(&taken), ["alpha", "beta"]);
// the boundary is `> max` on bytes: not `> max + 1`, and not a count of chars
let mut edge = vec![job("abc"), job("ab"), job("éé")];
let t = take_long(&mut edge, 2);
assert_eq!(names(&edge), ["ab"]);
assert_eq!(names(&t), ["abc", "éé"]);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/take-long.solution.rsRun ↗
// A job cannot be cloned or copied: it can only be moved.
struct Job {
name: String,
}
fn job(name: &str) -> Job {
Job {
name: name.to_string(),
}
}
// Remove every job whose name is longer than `max` bytes and return those jobs,
// in their original order. The jobs left in the list keep their order too.
fn take_long(jobs: &mut Vec<Job>, max: usize) -> Vec<Job> {
let mut kept = Vec::new();
let mut long = Vec::new();
for j in std::mem::take(jobs) {
if j.name.len() > max {
long.push(j);
} else {
kept.push(j);
}
}
*jobs = kept;
long
}
fn names(jobs: &[Job]) -> Vec<&str> {
jobs.iter().map(|j| j.name.as_str()).collect()
}
fn main() {
let mut jobs = vec![job("a"), job("backup"), job("cd"), job("deploy")];
let long = take_long(&mut jobs, 2);
assert_eq!(names(&jobs), ["a", "cd"]);
assert_eq!(names(&long), ["backup", "deploy"]);
let mut none: Vec<Job> = Vec::new();
assert!(take_long(&mut none, 3).is_empty());
assert!(none.is_empty());
let mut all_short = vec![job("x"), job("y")];
assert!(take_long(&mut all_short, 5).is_empty());
assert_eq!(names(&all_short), ["x", "y"]);
let mut all_long = vec![job("alpha"), job("beta")];
let taken = take_long(&mut all_long, 1);
assert!(all_long.is_empty());
assert_eq!(names(&taken), ["alpha", "beta"]);
// the boundary is `> max` on bytes: not `> max + 1`, and not a count of chars
let mut edge = vec![job("abc"), job("ab"), job("éé")];
let t = take_long(&mut edge, 2);
assert_eq!(names(&edge), ["ab"]);
assert_eq!(names(&t), ["abc", "éé"]);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more