Skip to content
Learn Rust
Theme

Loops

Basics · Lesson 11

Rust has three loops: for over a range or collection, while, and loop (forever, until break).

Coming from Gofor i in 1..4 counts 1, 2, 3 like for i := 1; i < 4; i++, while n > 0 is for n > 0, and loop is for {}. There is no three-part for and no n++, but break can hand back a value from a loop.

Coming from KotlinWatch the ranges: Rust's 1..4 stops before 4 like Kotlin's 1 until 4, and Rust's 1..=4 is Kotlin's 1..4. There is no n++ (write n += 1), and loop is while (true) where break can give back a value.

Coming from Pythonfor i in 1..4 is for i in range(1, 4) and loop is while True. The one new thing is that break tries * 10 can hand a value back out of a loop.

Coming from TypeScriptRust has no C-style for (let i = 1; i < 4; i++) and no ++. You loop over a range instead (for i in 1..4), and loop is while (true) where break can hand back a value.

Coming from Javafor i in 1..4 is for (int i = 1; i < 4; i++); Rust has no C-style three-part for and no ++, so you write n += 1. loop is while (true), except break can hand back a value.

Coming from C#for i in 1..4 is like foreach (var i in Enumerable.Range(1, 3)), except that 1..4 gives start and end (4 excluded) where Range gives start and count. Rust has no three-part for or ++. loop is while (true) where break value returns a result, so the result can be an ordinary let instead of a variable set inside the loop.

Coming from CRust has no three-part for (init; test; step): you loop over a range like 1..4 instead, and n++ is gone, so write n += 1. loop is for (;;), and break can carry a value out of it.

Coming from C++for i in 1..4 is a range-based for over a half-open range, like for (int i : std::views::iota(1, 4)) in C++20, and there is no classic three-part for or ++. loop is while (true) with a break that returns a value, so you don't need the lambda trick.

The quick comparison

Rustsrc/main.rsRun ↗
fn main() {
// 1, 2, 3 (the end is not included)
for i in 1..4 {
println!("for {i}");
}
let mut n = 3;
while n > 0 {
n -= 1;
}
// loop runs until break. break can hand back a value.
let mut tries = 0;
let found = loop {
tries += 1;
if tries == 3 {
break tries * 10;
}
};
println!("n={n} found={found}");
}
$ cargo run
for 1
for 2
for 3
n=0 found=30
package main
import "fmt"
func main() {
for i := 1; i < 4; i++ {
fmt.Println("for", i)
}
n := 3
for n > 0 {
n--
}
tries, found := 0, 0
for {
tries++
if tries == 3 {
found = tries * 10
break
}
}
fmt.Printf("n=%d found=%d\n", n, found)
}
$ go run main.go
for 1
for 2
for 3
n=0 found=30
fun main() {
for (i in 1 until 4) {
println("for $i")
}
var n = 3
while (n > 0) {
n--
}
var tries = 0
var found = 0
while (true) {
tries++
if (tries == 3) {
found = tries * 10
break
}
}
println("n=$n found=$found")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
for 1
for 2
for 3
n=0 found=30
# 1, 2, 3 (the end is not included)
for i in range(1, 4):
print(f"for {i}")
n = 3
while n > 0:
n -= 1
# no loop-with-value in Python: use while True and set a variable
tries = 0
while True:
tries += 1
if tries == 3:
found = tries * 10
break
print(f"n={n} found={found}")
$ python3 main.py
for 1
for 2
for 3
n=0 found=30
// 1, 2, 3 (the end is not included)
for (let i = 1; i < 4; i++) {
console.log(`for ${i}`);
}
let n = 3;
while (n > 0) {
n -= 1;
}
// break can't hand back a value, so set a variable before breaking
let tries = 0;
let found: number;
while (true) {
tries += 1;
if (tries === 3) {
found = tries * 10;
break;
}
}
console.log(`n=${n} found=${found}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js
for 1
for 2
for 3
n=0 found=30
public class Main {
public static void main(String[] args) {
// 1, 2, 3 (the end is not included)
for (int i = 1; i < 4; i++) {
System.out.println("for " + i);
}
int n = 3;
while (n > 0) {
n--;
}
// break can't hand back a value, so set a variable first
int tries = 0;
int found;
while (true) {
tries++;
if (tries == 3) {
found = tries * 10;
break;
}
}
System.out.println("n=" + n + " found=" + found);
}
}
$ javac Main.java && java Main
for 1
for 2
for 3
n=0 found=30
// 1, 2, 3
for (var i = 1; i < 4; i++)
{
Console.WriteLine($"for {i}");
}
var n = 3;
while (n > 0)
{
n--;
}
// C# break can't hand back a value, so keep it in a variable
var tries = 0;
int found;
while (true)
{
tries++;
if (tries == 3)
{
found = tries * 10;
break;
}
}
Console.WriteLine($"n={n} found={found}");
$ dotnet run
for 1
for 2
for 3
n=0 found=30
#include <stdio.h>
int main(void) {
// 1, 2, 3 (the end is not included)
for (int i = 1; i < 4; i++) {
printf("for %d\n", i);
}
int n = 3;
while (n > 0) {
n -= 1;
}
// C's break cannot hand back a value, so set a variable first
int tries = 0;
int found;
for (;;) {
tries += 1;
if (tries == 3) {
found = tries * 10;
break;
}
}
printf("n=%d found=%d\n", n, found);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
for 1
for 2
for 3
n=0 found=30
#include <iostream>
int main() {
// 1, 2, 3 (the end is not included)
for (int i = 1; i < 4; ++i) {
std::cout << "for " << i << "\n";
}
int n = 3;
while (n > 0) {
n -= 1;
}
// break can't hand back a value, so a lambda that returns does the job
int tries = 0;
const int found = [&] {
while (true) {
tries += 1;
if (tries == 3) {
return tries * 10;
}
}
}();
std::cout << "n=" << n << " found=" << found << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
for 1
for 2
for 3
n=0 found=30

What's different

  • 1..4 is 1, 2, 3: the end is left out. 1..=4 includes the 4.
  • There is no C-style for i := 0; i < n; i++. Use for i in 0..n.
  • (0..10).rev() counts down. (0..10).step_by(2) skips. A range that starts above its end, like 4..0, is empty: it does not count down.

Questions people ask

Can a for loop give back a value with break value, like loop does?

No. Only loop can hand back a value with break value. A for or while loop has the value (), and the compiler stops you if you write break value in one.

The reason: a for loop can finish without ever reaching break, for example on an empty list. Then there would be no value to assign. A loop can only end through break, so a value always comes out.

In practice you rarely need either. When you want a value from a loop, use an iterator method. Each one is an expression and says what you mean:

  • find: the first item that matches. It returns an Option, because there might be none.
  • position: the index of the first match, also an Option.
  • sum, count, max, any, all: one value from the list.
  • map(...).collect(): a new list.
Rustsrc/main.rs✗ won't compile
fn main() {
let nums = vec![3, 8, 12, 5];
let found = for n in &nums {
if *n > 10 { break *n; }
};
}
$ cargo build
error[E0571]: `break` with value from a `for` loop
 --> src/main.rs:4:22
  |
3 |     let found = for n in &nums {
  |                 -------------- you can't `break` with a value in a `for` loop
4 |         if *n > 10 { break *n; }
  |                      ^^^^^^^^ can only break with a value inside `loop` or breakable block
  |
help: use `break` on its own without a value inside this `for` loop
  |
4 -         if *n > 10 { break *n; }
4 +         if *n > 10 { break; }
Rustsrc/main.rs✓ compiles and runs
fn main() {
let nums = vec![3, 8, 12, 5];
// only `loop` can hand back a value with break
let mut i = 0;
let idx = loop {
if nums[i] > 10 { break i; }
i += 1;
};
// what people usually write: iterator methods are expressions
let first_big = nums.iter().find(|&&n| n > 10);
let position = nums.iter().position(|&n| n > 10);
let total: i32 = nums.iter().sum();
let squares: Vec<i32> = nums.iter().map(|n| n * n).collect();
println!("{idx} {first_big:?} {position:?} {total} {squares:?}");
}
$ cargo run
2 Some(12) Some(2) 28 [9, 64, 144, 25]
Gotcha

Using n++

Looks fine: in Go, Kotlin, TypeScript, Java, C#, C and C++ it runs fine; in Python it runs but gives a wrong answer. 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.Broken in Python. Rust catches it.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 mut n = 0;
n++;
println!("{n}");
}
package main
import "fmt"
func main() {
n := 0
n++
fmt.Println(n)
}
$ go run main.go✓ runs
1
fun main() {
var n = 0
n++
println(n)
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
1
n = 0
++n # n++ is a syntax error; ++n is two unary pluses
print(n)
$ python3 main.py! runs · wrong answer
0
let n = 0;
n++;
console.log(n);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
1
public class Main {
public static void main(String[] args) {
int n = 0;
n++;
System.out.println(n);
}
}
$ javac Main.java && java Main✓ runs
1
var n = 0;
n++;
Console.WriteLine(n);
$ dotnet run✓ runs
1
#include <stdio.h>
int main(void) {
int n = 0;
n++;
printf("%d\n", n);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
1
#include <iostream>
int main() {
int n = 0;
n++;
std::cout << n << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
1
What does the compiler say? Guess first, then open.
$ cargo build
error: Rust has no postfix increment operator
 --> src/main.rs:3:6
  |
3 |     n++;
  |      ^^ not a valid postfix operator
  |
help: use `+= 1` instead
  |
3 -     n++;
3 +     n += 1;
What happens in Go

Go runs this and prints 1. n++ is fine in Go as a statement on its own.

What happens in Kotlin

Kotlin runs this and prints 1. In Rust you write the same thing as n += 1.

What happens in Python

n++ is a syntax error in Python, but ++n runs and prints 0. It is read as two unary pluses, so n never changes.

What happens in TypeScript

Prints 1. n++ is normal TypeScript; Rust has no ++ and wants n += 1.

What happens in Java

Java has ++, so this prints 1. Rust has no ++; write n += 1 instead.

What happens in C#

n++ is everyday C# and prints 1. Rust has no ++ and asks for n += 1.

What happens in C

C has n++, so this prints 1. In Rust you write n += 1.

What happens in C++

C++ has ++, so this prints 1. In Rust you write n += 1.

What Rust sees

Rust has no ++ or --. Write n += 1.

Why Rust says no

Rust has no ++ or --; += 1 is the one way to add one. ++ inside other expressions (like a[i++] = i) is a classic source of confusion about what happens first. Go keeps n++ but only as a statement; Rust drops it.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let mut n = 0;
n += 1;
println!("{n}");
}
$ cargo run
1

The toolkit

Rust has three loops: for goes through a sequence of values, while repeats while a bool condition holds, and loop repeats until a break. while let is a while that tests a pattern. There is no three-part for and no do ... while (what replaces them). Outputs come from Linux on 64-bit ARM with Rust 1.99.1.

Loop Use it when Its value
for x in seq { } you have a range, an array or another sequence ()
while cond { } a bool decides whether to go again ()
while let Some(x) = expr { } you keep taking items until none is left ()
loop { } there is no natural condition; you leave with break whatever follows break

Only loop can break with a value (let x = loop { break 5; };). Statements and expressions shows the error for and while give if you try.

Ranges

A range is a value that stands for a run of numbers (or chars): a..b stops before b and a..=b includes it. The end is read once, when the loop starts.

Rustsrc/main.rsRun ↗
fn main() {
// a..b stops before b; a..=b includes b
for i in 1..4 {
println!("1..4: {i}");
}
for i in 1..=4 {
println!("1..=4: {i}");
}
// ranges of char work too
for c in 'a'..='c' {
println!("'a'..='c': {c}");
}
// counting down: reverse a range
for i in (1..=3).rev() {
println!("rev: {i}");
}
// every third number
for i in (0..10).step_by(3) {
println!("step_by(3): {i}");
}
// a range that starts above its end is empty: no error, no countdown
let mut runs = 0;
for _ in 4..0 {
runs += 1;
}
println!("runs of 4..0: {runs}");
// the end is read once, when the loop starts
let mut n = 3;
let mut passes = 0;
for _ in 0..n {
n = 5;
passes += 1;
}
println!("n is now {n}, passes: {passes}");
}
$ cargo run
1..4: 1
1..4: 2
1..4: 3
1..=4: 1
1..=4: 2
1..=4: 3
1..=4: 4
'a'..='c': a
'a'..='c': b
'a'..='c': c
rev: 3
rev: 2
rev: 1
step_by(3): 0
step_by(3): 3
step_by(3): 6
step_by(3): 9
runs of 4..0: 0
n is now 5, passes: 3

The trap is 4..0: it is not a countdown. It is empty, so the body never runs, with no error. rev and step_by are methods on the range (Iterators); wrap the range in parentheses before calling one. step_by counts from the front of whatever it receives, so order matters.

Rustsrc/main.rsRun ↗
fn main() {
// the order matters: step_by counts from the front of whatever it is given
print!("rev then step_by(3):");
for i in (1..10).rev().step_by(3) {
print!(" {i}");
}
println!();
print!("step_by(3) then rev:");
for i in (1..10).step_by(3).rev() {
print!(" {i}");
}
println!();
}
$ cargo run
rev then step_by(3): 9 6 3
step_by(3) then rev: 7 4 1

Looping over a list

A for loop takes anything that produces items one at a time, such as an array. .iter() hands out references to the items; .enumerate() pairs each with its position, and (i, name) unpacks the pair:

Rustsrc/main.rsRun ↗
fn main() {
let scores = [10, 20, 30];
// an array can be looped over directly
for s in scores {
println!("score: {s}");
}
// enumerate pairs each item with its position
let names = ["ann", "bo"];
for (i, name) in names.iter().enumerate() {
println!("name {i}: {name}");
}
}
$ cargo run
score: 10
score: 20
score: 30
name 0: ann
name 1: bo

Looping Over a Collection covers for x in v, &v and &mut v.

The loop variable

The name after for is a new variable on every pass, and it belongs to the loop. It cannot be assigned unless you write mut, and even then only that pass sees the change. Python’s for and TypeScript’s for...of work the same way; the three-part for of C, Java, C#, Go and TypeScript keeps one counter that the body can change.

Rustsrc/main.rsRun ↗
fn main() {
let mut passes = 0;
// `mut i` is allowed, but it is a copy for this pass only
for mut i in 0..5 {
if i == 1 {
i += 2;
}
passes += 1;
println!("i: {i}");
}
println!("passes: {passes}");
}
$ cargo run
i: 0
i: 3
i: 2
i: 3
i: 4
passes: 5

Five passes, whatever the body does to i. Without mut the assignment does not compile, and after the loop the name is gone:

Rustsrc/main.rs✗ won't compile
fn main() {
for i in 0..3 {
i += 1;
println!("{i}");
}
}
$ cargo buildWhat is E0384?
error[E0384]: cannot assign twice to immutable variable `i`
 --> src/main.rs:3:9
  |
2 |     for i in 0..3 {
  |         - first assignment to `i`
3 |         i += 1;
  |         ^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     for mut i in 0..3 {
  |         +++
Rustsrc/main.rs✗ won't compile
fn main() {
for i in 0..3 {
println!("inside: {i}");
}
println!("after: {i}");
}
$ cargo buildWhat is E0425?
error[E0425]: cannot find value `i` in this scope
 --> src/main.rs:5:23
  |
5 |     println!("after: {i}");
  |                       ^ not found in this scope

loop, break and continue

break leaves the loop. continue skips the rest of this pass. In a loop, break value makes the whole loop evaluate to that value, so the result can be a plain let instead of a mut variable. A loop with the test at the end runs the body first, as do ... while does elsewhere:

Rustsrc/main.rsRun ↗
fn main() {
// loop is the only loop that gives back a value: break carries it out
let mut guess = 1;
let first_over_100 = loop {
guess *= 3;
if guess > 100 {
break guess;
}
};
println!("first power of 3 over 100: {first_over_100}");
// a do-while: run the body once, then decide
let mut n = 0;
loop {
n += 5;
if n >= 3 {
break;
}
}
println!("n after the body ran once: {n}");
}
$ cargo run
first power of 3 over 100: 243
n after the body ran once: 5

A step you write yourself

A step that is not +1 (i *= 2, two cursors walking towards each other) is a while with the step in the body. The catch: continue skips the rest of the pass, including a step or test written at the end. The last two loops below would run on, so each has a guard:

Rustsrc/main.rsRun ↗
fn main() {
// a step that is not +1 is a `while` with the step written by hand
print!("doubling:");
let mut i = 1;
while i < 100 {
print!(" {i}");
i *= 2;
}
println!();
// `continue` skips the rest of the pass, including a step written at the end
let mut j = 0;
let mut passes = 0;
while j < 3 && passes < 6 {
// (the `passes` guard only stops this demo)
passes += 1;
if j == 1 {
continue;
}
j += 1;
}
println!("while: j = {j}, passes = {passes}");
// the same goes for the test at the end of a `loop` that stands in for `do ... while`
let mut k = 0;
let mut passes = 0;
loop {
k += 1;
passes += 1;
if passes == 6 {
break; // guard, only for this demo
}
if k >= 3 {
continue; // in C, `continue` in a do-while goes on to the test
}
if !(k < 3) {
break;
}
}
println!("loop: k = {k}, passes = {passes}");
}
$ cargo run
doubling: 1 2 4 8 16 32 64
while: j = 1, passes = 6
loop: k = 6, passes = 6

Labels

A name written 'outer: in front of a loop lets break 'outer and continue 'outer act on that loop from inside an inner one. (The ' is the one lifetimes use; Rust at a glance lists both.) break 'label value works on a labelled loop, and a plain block can carry a label too and be left with a value.

Rustsrc/main.rsRun ↗
fn main() {
// continue 'outer skips to the next n as soon as one divisor is found
'outer: for n in 2..20 {
for d in 2..n {
if n % d == 0 {
continue 'outer;
}
}
println!("prime: {n}");
}
// break 'label value leaves a labelled loop and gives back a value
let pair = 'search: loop {
for a in 1..10 {
for b in a..10 {
if a * b == 24 && a + b == 10 {
break 'search (a, b);
}
}
}
break (0, 0);
};
println!("pair: {pair:?}");
}
$ cargo run
prime: 2
prime: 3
prime: 5
prime: 7
prime: 11
prime: 13
prime: 17
prime: 19
pair: (4, 6)

A break inside a match leaves the enclosing loop, not the match (why there is no fall-through):

Rustsrc/main.rsRun ↗
fn main() {
// `match` is not a loop and not a switch: `break` here leaves the `for`
for n in 1..=5 {
match n {
3 => break,
_ => println!("n: {n}"),
}
}
println!("after the loop");
}
$ cargo run
n: 1
n: 2
after the loop

while let

while let PATTERN = expr runs the body while the pattern fits, and stops the first time it does not. The usual partner is Vec::pop, which returns Some(item) while the list has items and None when it is empty (Option, Vectors). It suits work that the loop itself adds to. This one walks 1 to 7 as a tree (n has children 2n and 2n + 1), taking the last child first:

Rustsrc/main.rsRun ↗
fn main() {
// a worklist: each pass may add more work, which `for x in &work` would not allow
let mut work = vec![1];
let mut order = Vec::new();
while let Some(n) = work.pop() {
order.push(n);
if n * 2 <= 7 {
work.push(n * 2);
work.push(n * 2 + 1);
}
}
println!("visited: {order:?}");
println!("work left: {work:?}");
}
$ cargo run
visited: [1, 3, 7, 6, 2, 5, 4]
work left: []

A for over the same list does not compile: the loop is still using the list when push changes it (Borrowing explains the rule):

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

Counting down with usize

Lengths and indexes are usize, an unsigned integer (Declarations and types). Rust will not index with an i32, and an unsigned counter goes below zero:

Rustsrc/main.rs✗ won't compile
fn main() {
let items = [5, 6, 7];
let i: i32 = 2;
println!("items[i] = {}", items[i]);
}
$ cargo buildWhat is E0277?
error[E0277]: the type `[{integer}]` cannot be indexed by `i32`
 --> src/main.rs:4:37
  |
4 |     println!("items[i] = {}", items[i]);
  |                                     ^ slice indices are of type `usize` or ranges of `usize`
  |
  = help: the trait `SliceIndex<[{integer}]>` is not implemented for `i32`
  ... (notes pointing into the standard library trimmed)
Rustsrc/main.rs! panics at runtimeRun ↗
#![allow(unused_comparisons)] // silences the compiler's "this is always true" warning
fn main() {
let items = [5, 6, 7];
let mut i = items.len() - 1;
// a usize is never below 0, so `i >= 0` is always true
while i >= 0 {
println!("items[{i}] = {}", items[i]);
i -= 1;
}
}
$ cargo run
items[2] = 7
items[1] = 6
items[0] = 5

thread 'main' (1) panicked at src/main.rs:9:9:
attempt to subtract with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The panic comes from i -= 1 at 0: “attempt to subtract with overflow”. The compiler also warns about the test i >= 0 by default; here that lint is turned into an error to show the message in full:

Rustsrc/main.rs✗ won't compile
#![deny(unused_comparisons)] // this lint is a warning by default; deny makes it an error
fn main() {
let items = [5, 6, 7];
let mut i = items.len() - 1;
while i >= 0 {
println!("items[{i}] = {}", items[i]);
i -= 1;
}
}
$ cargo build
error: comparison is useless due to type limits
 --> src/main.rs:6:11
  |
6 |     while i >= 0 {
  |           ^^^^^^
  |
note: the lint level is defined here
 --> src/main.rs:1:9
  |
1 | #![deny(unused_comparisons)] // this lint is a warning by default; deny makes it an error
  |         ^^^^^^^^^^^^^^^^^^

That panic is a debug-build check (a plain cargo run; Hello, World covers the build kinds). A --release build turns overflow-checks off (the Rust Book, Cargo profiles), and i -= 1 then wraps to the largest usize. This program would still panic, at items[i], as the next one does; a loop that does not index would have nothing to stop it.

Rustsrc/main.rs! panics at runtimeRun ↗
fn main() {
let items = [5, 6, 7];
// what `i -= 1` gives at 0 when overflow checks are off: wrapping_sub always wraps
let i = std::hint::black_box(0usize).wrapping_sub(1);
println!("i: {i}");
println!("items[i] = {}", items[i]);
}
$ cargo run
i: 18446744073709551615

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

Count down with a reversed range instead. It never subtracts, and an empty list is just an empty range:

Rustsrc/main.rsRun ↗
fn show(items: &[i32]) {
// (0..len).rev() counts len-1, ..., 0 and never subtracts below zero
for i in (0..items.len()).rev() {
println!("items[{i}] = {}", items[i]);
}
println!("done, {} items", items.len());
}
fn main() {
show(&[5, 6, 7]);
show(&[]);
}
$ cargo run
items[2] = 7
items[1] = 6
items[0] = 5
done, 3 items
done, 0 items

Your language, in Rust

Going deeper

What for does

The Reference says for calls IntoIterator::into_iter on the expression and then calls next() in a loop, leaving at None. That is why anything that produces items works, and why a range is an ordinary value rather than special loop syntax (IntoIterator is a trait, a set of abilities; Traits). The match below takes Some(i) while an item comes and None at the end:

Rustsrc/main.rsRun ↗
fn main() {
// what `for i in 0..3 { ... }` does: ask for the next item until there is none
let mut it = 0..3;
loop {
match it.next() {
Some(i) => println!("manual: {i}"),
None => break,
}
}
// a range is an ordinary value with fields and methods
let r = 2..5;
println!("start: {}, end: {}", r.start, r.end);
println!("len: {}", r.len());
println!("contains 4: {}", r.contains(&4));
println!("contains 5: {}", r.contains(&5));
}
$ cargo run
manual: 0
manual: 1
manual: 2
start: 2, end: 5
len: 3
contains 4: true
contains 5: false

..= reaches the last value

0..=255u8 includes 255, the largest u8, and the loop still ends. 0..255u8 stops one short:

Rustsrc/main.rsRun ↗
fn main() {
let mut inclusive = 0;
for _ in 0..=255u8 {
inclusive += 1;
}
let mut exclusive = 0;
for _ in 0..255u8 {
exclusive += 1;
}
println!("0..=255u8 runs: {inclusive}");
println!("0..255u8 runs: {exclusive}");
}
$ cargo run
0..=255u8 runs: 256
0..255u8 runs: 255

No float ranges

0.0..1.0 is a valid range of f64, but Range implements Iterator only for select types, particularly integers. Count with an integer, then scale:

Rustsrc/main.rs✗ won't compile
fn main() {
for x in 0.0..1.0 {
println!("{x}");
}
}
$ cargo buildWhat is E0277?
error[E0277]: `std::ops::Range<{float}>` is not an iterator
 --> src/main.rs:2:14
  |
2 |     for x in 0.0..1.0 {
  |              ^^^^^^^^ `Range<{float}>` is not an iterator
  |
  = help: the nightly-only, unstable trait `Step` is not implemented for `{float}`
  = note: `Range` only implements `Iterator` for select types in the standard library, particularly integers; to see the full list of types, see the documentation for the unstable `Step` trait
  = help: the following other types implement trait `Step`:
            i128
            i16
            i32
            i64
            i8
            isize
            u128
            u16
          and 4 others
  = note: required for `std::ops::Range<{float}>` to implement `Iterator`
  = note: required for `std::ops::Range<{float}>` to implement `IntoIterator`
Rustsrc/main.rsRun ↗
fn main() {
// count with an integer, then scale
for i in 0..5 {
let x = i as f64 * 0.25;
println!("x: {x}");
}
}
$ cargo run
x: 0
x: 0.25
x: 0.5
x: 0.75
x: 1

Practice

Try it

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

Collatz steps, then a first square

collatz_steps counts the steps to reach 1 (even: halve, odd: triple and add one) with a while loop; 6 takes 8 steps and 1 takes none. first_square_over finds the smallest k whose square exceeds a limit with a loop that ends in break k.

Starterexercises/collatz.rs
/// How many steps it takes to reach 1: an even n becomes n / 2, an odd n becomes 3 * n + 1.
/// `n` is at least 1. 6 -> 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 is 8 steps.
fn collatz_steps(n: u64) -> u32 {
todo!("a `while` loop that runs until n is 1 and counts the steps")
}
/// The smallest k whose square is greater than `limit`.
fn first_square_over(limit: u32) -> u32 {
todo!("a `loop` that tries k = 1, 2, ... and ends with `break k` (no `mut` result needed)")
}
fn main() {
assert_eq!(collatz_steps(1), 0);
assert_eq!(collatz_steps(2), 1);
assert_eq!(collatz_steps(6), 8);
assert_eq!(collatz_steps(27), 111);
assert_eq!(first_square_over(0), 1);
assert_eq!(first_square_over(1), 2);
assert_eq!(first_square_over(24), 5);
assert_eq!(first_square_over(25), 6);
assert_eq!(first_square_over(100), 11);
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/collatz.solution.rsRun ↗
/// How many steps it takes to reach 1: an even n becomes n / 2, an odd n becomes 3 * n + 1.
/// `n` is at least 1. 6 -> 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 is 8 steps.
fn collatz_steps(mut n: u64) -> u32 {
let mut steps = 0;
while n != 1 {
if n % 2 == 0 {
n /= 2;
} else {
n = 3 * n + 1;
}
steps += 1;
}
steps
}
/// The smallest k whose square is greater than `limit`.
fn first_square_over(limit: u32) -> u32 {
let mut k = 1;
loop {
if k * k > limit {
break k;
}
k += 1;
}
}
fn main() {
assert_eq!(collatz_steps(1), 0);
assert_eq!(collatz_steps(2), 1);
assert_eq!(collatz_steps(6), 8);
assert_eq!(collatz_steps(27), 111);
assert_eq!(first_square_over(0), 1);
assert_eq!(first_square_over(1), 2);
assert_eq!(first_square_over(24), 5);
assert_eq!(first_square_over(25), 6);
assert_eq!(first_square_over(100), 11);
println!("all checks passed");
}
$ cargo run
all checks passed

Count the primes on a stack

Count the primes in a Vec by popping numbers off its end (the function owns the list; Ownership explains). stack.pop() gives Some(n) while there are numbers and None when the list is empty (Option), so while let Some(n) = stack.pop() is the loop. Try the divisors 2..n; when one divides n, a labelled continue goes to the next popped number. 0 and 1 are not prime.

Starterexercises/count-primes.rs
/// How many of the numbers in `stack` are prime (2, 3, 5, 7, 11, ...). 0 and 1 are not.
fn count_primes(mut stack: Vec<u32>) -> u32 {
todo!("`while let Some(n) = stack.pop()`; a labelled `continue` when a divisor is found")
}
fn main() {
assert_eq!(count_primes(vec![2, 3, 4, 5, 6, 7]), 4);
assert_eq!(count_primes(vec![97, 100]), 1);
assert_eq!(count_primes(vec![9, 15, 21]), 0);
// 0 and 1 are not prime, and an empty stack is fine
assert_eq!(count_primes(vec![0, 1]), 0);
assert_eq!(count_primes(vec![]), 0);
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/count-primes.solution.rsRun ↗
/// How many of the numbers in `stack` are prime (2, 3, 5, 7, 11, ...). 0 and 1 are not.
fn count_primes(mut stack: Vec<u32>) -> u32 {
let mut primes = 0;
'next: while let Some(n) = stack.pop() {
if n < 2 {
continue;
}
for d in 2..n {
if n % d == 0 {
continue 'next;
}
}
primes += 1;
}
primes
}
fn main() {
assert_eq!(count_primes(vec![2, 3, 4, 5, 6, 7]), 4);
assert_eq!(count_primes(vec![97, 100]), 1);
assert_eq!(count_primes(vec![9, 15, 21]), 0);
// 0 and 1 are not prime, and an empty stack is fine
assert_eq!(count_primes(vec![0, 1]), 0);
assert_eq!(count_primes(vec![]), 0);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more