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
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());}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)}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")}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}")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);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); }}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)}]");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;}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";}hi ana hi bo 0: 10 1: 20 2: 30 2 names, [10, 20, 30]
What's different
for x in vtakes each item out ofv; aVecis used up (an array of numbers is copied, so it stays).for x in &vborrows each item;vstays.for x in &mut vborrows each item so you can change it.&vis the same asv.iter(), and&mut vis the same asv.iter_mut(). With the borrowing formsxis a reference, so you may need*xto 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
forover it: Rust refuses to compile it, andretainor a list of positions are the usual fixes.
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.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")}hi ana hi bo 2 names
fun main() { val names = listOf("ana", "bo") for (n in names) { println("hi $n") } println("${names.size} names")}hi ana hi bo 2 names
names = ["ana", "bo"]for n in names: print(f"hi {n}")print(f"{len(names)} names")hi ana hi bo 2 names
const names = ["ana", "bo"];for (const n of names) { console.log(`hi ${n}`);}console.log(`${names.length} names`);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"); }}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");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;}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";}hi ana hi bo 2 names
What does the compiler say? Guess first, then open.
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)
Go runs this and prints both names, then 2 names. range leaves the slice alone, so it is still there afterwards.
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.
Python prints both names and then 2 names. A for loop does not take the list away, so it is still there afterwards.
Prints both names, then 2 names. for...of does not take the array away, so it is still there afterwards.
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.
foreach does not take the list away, so names is still there afterwards and this prints 2 names.
A C index loop reads the array and nothing about it makes names unusable, so the length is still 2 afterwards.
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.
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.
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.
fn main() { let names = vec![String::from("ana"), String::from("bo")]; for n in &names { println!("hi {n}"); } println!("{} names", names.len());}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:
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)); }}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:
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 }}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
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:?}");}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:
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}"); }}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:
fn main() { let v = vec![10, 20, 30]; for i in 0..=v.len() { println!("v[{i}] = {}", v[i]); }}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:
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:?}");}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:
fn main() { let mut v = vec![1, 2, 3, 4]; for x in &v { if x % 2 == 0 { v.remove(0); } } println!("v: {v:?}");}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:
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:?}");}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:
// `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:?}");}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 inv, which is still yours.drain(..)takes everything, and works on a&mut Vecparameter too.std::mem::take(&mut v)moves the whole list out and leaves an empty one in its place (Mutable borrows coverstake). It is one way to take the items of a&mut Vecparameter, wherefor x in vwould give you&mutitems, not owned ones.
Maps and text
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!();}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
Coming from C or C++
| In C / C++ | In Rust |
|---|---|
C++ for (const auto& x : v) |
for x in &v |
C++ for (auto& x : v) |
for x in &mut v |
C++ for (auto x : v): a copy of each item |
for x in v.iter().cloned(): .cloned() copies any item that can be cloned, and .copied() is the same for plain numbers. for x in v moves instead |
C++20 for (auto& x : v | std::views::reverse) |
for x in v.iter().rev() |
C++ for (size_t i = 0; i < v.size(); ++i) |
for i in 0..v.len(), or for x in &v |
C for (size_t i = n; i-- > 0;) |
for x in a.iter().rev() |
C for (int *p = a; p < a + n; p++), or an index and a separate n |
for x in &a: the array or slice knows its length |
C++20 std::erase_if(v, p), or v.erase(std::remove_if(…), v.end()) |
v.retain(|x| !p(x)): retain keeps the items the closure says true for, remove_if removes them |
The three C++ forms in one run:
#include <iostream>#include <vector>
int main() { std::vector<int> v{1, 2, 3};
for (auto x : v) { x *= 10; // x is a copy } std::cout << "after auto x: " << v[0] << " " << v[1] << " " << v[2] << "\n";
for (auto& x : v) { x *= 10; // x refers to the item } std::cout << "after auto& x: " << v[0] << " " << v[1] << " " << v[2] << "\n";}after auto x: 1 2 3 after auto& x: 10 20 30
Changing a vector inside a range-for is undefined behaviour in C++, and that does not need a reallocation. The loop caches end() before it starts, and push_back invalidates end() even when there is room (cppreference, how the loop is defined). It crashes visibly only when the vector reallocates (Mutable borrows runs that under AddressSanitizer). Below, the vector has spare room (reserve(100)), and erase is the other mistake C++ programmers write:
#include <algorithm>#include <iostream>#include <vector>
int main() { // Room to spare: push_back does not reallocate, yet the loop cached end() before it started. std::vector<int> v{1, 2, 3, 4, 5, 6}; v.reserve(100); int passes = 0; for (int x : v) { passes++; if (x == 2) v.push_back(99); } std::cout << "push_back: " << passes << " passes, size " << v.size() << "\n";
// erase in a range-for: no sanitizer report either. std::vector<int> w{1, 2, 3, 4, 5, 6}; for (int x : w) { if (x % 2 == 0) w.erase(std::find(w.begin(), w.end(), x)); } std::cout << "erase evens, left:"; for (int x : w) std::cout << " " << x; std::cout << "\n";}push_back: 6 passes, size 7 erase evens, left: 1
The sanitizer reported nothing. The push_back loop made its six original passes and never reached the 99 it pushed, and the erase loop left 1 where retain would leave 1 3 5. The behaviour is undefined, so another compiler or run may differ. Both are a compile error 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:
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);}(&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:
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");}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.
A borrowing loop owns nothing, so a break drops nothing:
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());}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:
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());}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.
// 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
// 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");}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.
// 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
// 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");}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.
// 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
// 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");}all checks passed
Read more
- The Rust Book, chapter 3.5 Control Flow, the section “Looping Through a Collection with for”.
- Rust by Example: for and range, which shows
iter,into_iteranditer_mutside by side. IntoIteratorin the standard library docs, andVec::retain.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.