Rust - Loops
Overview
Estimated time: 45–55 minutes
Master all loop types in Rust: infinite loops with loop
, conditional loops with while
, and iteration with for
. Learn about loop control with break
and continue
, loop labels, and common iteration patterns.
Learning Objectives
- Understand the three types of loops in Rust:
loop
,while
, andfor
. - Master loop control with
break
andcontinue
statements. - Learn loop labels for nested loop control.
- Understand iteration patterns and iterators.
- Learn when to use each type of loop effectively.
Prerequisites
The Loop Statement
The loop
keyword creates an infinite loop that runs until explicitly broken.
Basic Infinite Loop
fn main() {
let mut count = 0;
loop {
count += 1;
println!("Count: {}", count);
if count >= 5 {
break; // Exit the loop
}
}
println!("Loop finished!");
}
Expected Output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Loop finished!
Returning Values from Loops
Loops are expressions and can return values when broken:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Return a value when breaking
}
};
println!("The result is: {}", result);
// Another example: finding first even number > 10
let mut num = 11;
let first_even = loop {
if num % 2 == 0 {
break num;
}
num += 1;
};
println!("First even number > 10: {}", first_even);
}
Expected Output:
The result is: 20
First even number > 10: 12
While Loops
While loops continue executing as long as a condition is true.
Basic While Loop
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
// Another example: calculating sum
let mut sum = 0;
let mut i = 1;
while i <= 10 {
sum += i;
i += 1;
}
println!("Sum of 1 to 10: {}", sum);
}
Expected Output:
3!
2!
1!
LIFTOFF!!!
Sum of 1 to 10: 55
While Loop with Complex Conditions
fn main() {
let mut x = 1;
let mut y = 1;
// Fibonacci sequence while sum < 100
while x + y < 100 {
let temp = x + y;
x = y;
y = temp;
println!("Fibonacci: {}", x);
}
// Processing input until condition met
let mut attempts = 0;
let mut success = false;
while !success && attempts < 5 {
attempts += 1;
// Simulate some operation
let random_success = attempts > 3; // Simulate success after 3 attempts
if random_success {
success = true;
println!("Success on attempt {}!", attempts);
} else {
println!("Attempt {} failed", attempts);
}
}
if !success {
println!("Failed after {} attempts", attempts);
}
}
Expected Output:
Fibonacci: 1
Fibonacci: 2
Fibonacci: 3
Fibonacci: 5
Fibonacci: 8
Fibonacci: 13
Fibonacci: 21
Fibonacci: 34
Fibonacci: 55
Fibonacci: 89
Attempt 1 failed
Attempt 2 failed
Attempt 3 failed
Success on attempt 4!
For Loops
For loops are used to iterate over collections and ranges. They're the most common type of loop in Rust.
Iterating Over Ranges
fn main() {
// Exclusive range (1 to 4)
println!("Counting from 1 to 5 (exclusive):");
for number in 1..5 {
println!("{}", number);
}
// Inclusive range (1 to 5)
println!("\nCounting from 1 to 5 (inclusive):");
for number in 1..=5 {
println!("{}", number);
}
// Reverse range
println!("\nCountdown:");
for number in (1..=5).rev() {
println!("{}", number);
}
println!("Blast off!");
}
Expected Output:
Counting from 1 to 5 (exclusive):
1
2
3
4
Counting from 1 to 5 (inclusive):
1
2
3
4
5
Countdown:
5
4
3
2
1
Blast off!
Iterating Over Arrays and Vectors
fn main() {
let arr = [10, 20, 30, 40, 50];
// Iterate over array elements
println!("Array elements:");
for element in arr {
println!("{}", element);
}
// Iterate with index using enumerate
println!("\nArray with indices:");
for (index, value) in arr.iter().enumerate() {
println!("Index {}: {}", index, value);
}
// Vector iteration
let vec = vec!["apple", "banana", "cherry"];
println!("\nVector elements:");
for fruit in &vec { // Borrow each element
println!("{}", fruit);
}
// Mutable iteration
let mut numbers = vec![1, 2, 3, 4, 5];
println!("\nDoubling numbers:");
for num in &mut numbers { // Mutable borrow
*num *= 2;
println!("{}", num);
}
println!("Final vector: {:?}", numbers);
}
Expected Output:
Array elements:
10
20
30
40
50
Array with indices:
Index 0: 10
Index 1: 20
Index 2: 30
Index 3: 40
Index 4: 50
Vector elements:
apple
banana
cherry
Doubling numbers:
2
4
6
8
10
Final vector: [2, 4, 6, 8, 10]
Iterating Over Characters and Strings
fn main() {
let text = "Hello, Rust!";
// Iterate over characters
println!("Characters:");
for ch in text.chars() {
println!("{}", ch);
}
// Iterate over bytes
println!("\nBytes:");
for byte in text.bytes() {
println!("{}", byte);
}
// Iterate over words
println!("\nWords:");
for word in text.split_whitespace() {
println!("{}", word);
}
// Count vowels example
let mut vowel_count = 0;
for ch in text.to_lowercase().chars() {
match ch {
'a' | 'e' | 'i' | 'o' | 'u' => vowel_count += 1,
_ => {}
}
}
println!("\nVowel count in '{}': {}", text, vowel_count);
}
Expected Output:
Characters:
H
e
l
l
o
,
R
u
s
t
!
Bytes:
72
101
108
108
111
44
32
82
117
115
116
33
Words:
Hello,
Rust!
Vowel count in 'Hello, Rust!': 3
Loop Control: Break and Continue
Using Break
break
exits the loop immediately:
fn main() {
// Break out of infinite loop
let mut counter = 0;
loop {
counter += 1;
if counter == 3 {
println!("Breaking at counter = {}", counter);
break;
}
println!("Counter: {}", counter);
}
// Break with return value
let result = loop {
counter += 1;
if counter > 10 {
break counter - 10;
}
};
println!("Result: {}", result);
// Break in for loop
println!("\nSearching for number 7:");
for num in 1..=10 {
if num == 7 {
println!("Found 7!");
break;
}
println!("Checking {}", num);
}
}
Expected Output:
Counter: 1
Counter: 2
Breaking at counter = 3
Result: 1
Searching for number 7:
Checking 1
Checking 2
Checking 3
Checking 4
Checking 5
Checking 6
Found 7!
Using Continue
continue
skips the rest of the current iteration:
fn main() {
// Skip even numbers
println!("Odd numbers from 1 to 10:");
for num in 1..=10 {
if num % 2 == 0 {
continue; // Skip even numbers
}
println!("{}", num);
}
// Processing with conditions
let numbers = vec![1, -2, 3, -4, 5, -6];
let mut positive_sum = 0;
println!("\nProcessing numbers:");
for num in numbers {
if num < 0 {
println!("Skipping negative number: {}", num);
continue;
}
positive_sum += num;
println!("Adding positive number: {}", num);
}
println!("Sum of positive numbers: {}", positive_sum);
}
Expected Output:
Odd numbers from 1 to 10:
1
3
5
7
9
Processing numbers:
Adding positive number: 1
Skipping negative number: -2
Adding positive number: 3
Skipping negative number: -4
Adding positive number: 5
Skipping negative number: -6
Sum of positive numbers: 9
Loop Labels
Loop labels allow you to break or continue outer loops from inner loops.
Nested Loops with Labels
fn main() {
println!("Searching for coordinate (2, 3):");
'outer: for x in 0..5 {
for y in 0..5 {
println!("Checking ({}, {})", x, y);
if x == 2 && y == 3 {
println!("Found target at ({}, {})!", x, y);
break 'outer; // Break out of the outer loop
}
}
}
println!("Search complete!");
// Another example: continue outer loop
println!("\nSkipping rows with even x:");
'rows: for x in 1..=4 {
if x % 2 == 0 {
println!("Skipping row {}", x);
continue 'rows;
}
for y in 1..=3 {
println!(" ({}, {})", x, y);
}
}
}
Expected Output:
Searching for coordinate (2, 3):
Checking (0, 0)
Checking (0, 1)
Checking (0, 2)
Checking (0, 3)
Checking (0, 4)
Checking (1, 0)
Checking (1, 1)
Checking (1, 2)
Checking (1, 3)
Checking (1, 4)
Checking (2, 0)
Checking (2, 1)
Checking (2, 2)
Checking (2, 3)
Found target at (2, 3)!
Search complete!
Skipping rows with even x:
(1, 1)
(1, 2)
(1, 3)
Skipping row 2
(3, 1)
(3, 2)
(3, 3)
Complex Label Usage
fn main() {
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
let target = 5;
let mut found = false;
'search: for (row_idx, row) in matrix.iter().enumerate() {
for (col_idx, &value) in row.iter().enumerate() {
println!("Checking matrix[{}][{}] = {}", row_idx, col_idx, value);
if value == target {
println!("Found {} at position ({}, {})", target, row_idx, col_idx);
found = true;
break 'search;
}
}
}
if !found {
println!("Target {} not found in matrix", target);
}
}
Expected Output:
Checking matrix[0][0] = 1
Checking matrix[0][1] = 2
Checking matrix[0][2] = 3
Checking matrix[1][0] = 4
Checking matrix[1][1] = 5
Found 5 at position (1, 1)
Iterator Methods and Advanced Patterns
Iterator Combinators
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Chain multiple operations
let result: Vec = numbers
.iter()
.filter(|&&x| x % 2 == 0) // Keep even numbers
.map(|&x| x * x) // Square them
.collect(); // Collect into vector
println!("Even numbers squared: {:?}", result);
// Find first element matching condition
let first_big = numbers
.iter()
.find(|&&x| x > 5);
match first_big {
Some(value) => println!("First number > 5: {}", value),
None => println!("No number > 5 found"),
}
// Enumerate with iterator
println!("\nNumbers with positions:");
for (pos, value) in numbers.iter().enumerate() {
if pos % 2 == 0 { // Only even positions
println!("Position {}: {}", pos, value);
}
}
}
Expected Output:
Even numbers squared: [4, 16, 36, 64, 100]
First number > 5: 6
Numbers with positions:
Position 0: 1
Position 2: 3
Position 4: 5
Position 6: 7
Position 8: 9
Custom Iterator Patterns
fn main() {
// Range with step
println!("Every 3rd number from 0 to 20:");
for i in (0..=20).step_by(3) {
println!("{}", i);
}
// Zip two iterators
let names = vec!["Alice", "Bob", "Charlie"];
let ages = vec![25, 30, 35];
println!("\nPeople:");
for (name, age) in names.iter().zip(ages.iter()) {
println!("{} is {} years old", name, age);
}
// Take and skip
let numbers: Vec = (1..=10).collect();
println!("\nFirst 5 numbers:");
for num in numbers.iter().take(5) {
println!("{}", num);
}
println!("\nLast 5 numbers (skip first 5):");
for num in numbers.iter().skip(5) {
println!("{}", num);
}
}
Expected Output:
Every 3rd number from 0 to 20:
0
3
6
9
12
15
18
People:
Alice is 25 years old
Bob is 30 years old
Charlie is 35 years old
First 5 numbers:
1
2
3
4
5
Last 5 numbers (skip first 5):
6
7
8
9
10
Practical Examples
Input Processing Loop
fn main() {
// Simulate processing user input
let inputs = vec!["5", "10", "invalid", "15", "quit"];
let mut sum = 0;
let mut count = 0;
for input in inputs {
if input == "quit" {
println!("Quitting...");
break;
}
match input.parse::() {
Ok(num) => {
sum += num;
count += 1;
println!("Added {}, running sum: {}", num, sum);
}
Err(_) => {
println!("Invalid input '{}', skipping", input);
continue;
}
}
}
if count > 0 {
println!("Average: {:.2}", sum as f64 / count as f64);
}
}
Expected Output:
Added 5, running sum: 5
Added 10, running sum: 15
Invalid input 'invalid', skipping
Added 15, running sum: 30
Quitting...
Average: 10.00
Game Loop Pattern
fn main() {
let mut player_health = 100;
let mut enemy_health = 80;
let mut turn = 1;
println!("Battle begins!");
loop {
println!("\n--- Turn {} ---", turn);
println!("Player health: {}, Enemy health: {}", player_health, enemy_health);
// Player attacks (simulate with simple calculation)
let player_damage = 15 + (turn % 3) * 5; // Variable damage
enemy_health -= player_damage;
println!("Player deals {} damage!", player_damage);
if enemy_health <= 0 {
println!("Enemy defeated! Player wins!");
break;
}
// Enemy attacks
let enemy_damage = 12 + (turn % 2) * 3; // Variable damage
player_health -= enemy_damage;
println!("Enemy deals {} damage!", enemy_damage);
if player_health <= 0 {
println!("Player defeated! Enemy wins!");
break;
}
turn += 1;
// Safety check to prevent infinite loops
if turn > 10 {
println!("Battle takes too long, ending in a draw!");
break;
}
}
println!("Battle ended after {} turns", turn);
}
Expected Output:
Battle begins!
--- Turn 1 ---
Player health: 100, Enemy health: 80
Player deals 20 damage!
Enemy deals 15 damage!
--- Turn 2 ---
Player health: 85, Enemy health: 60
Player deals 25 damage!
Enemy deals 12 damage!
--- Turn 3 ---
Player health: 73, Enemy health: 35
Player deals 15 damage!
Enemy deals 15 damage!
--- Turn 4 ---
Player health: 58, Enemy health: 20
Player deals 20 damage!
Enemy defeated! Player wins!
Battle ended after 4 turns
Performance Considerations
Iterator Efficiency
fn main() {
let large_vec: Vec = (1..=1000).collect();
// Efficient: iterator adaptors are lazy
let sum: i32 = large_vec
.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 2)
.sum();
println!("Sum of doubled even numbers: {}", sum);
// Less efficient: collecting intermediate results
let even_numbers: Vec = large_vec
.iter()
.filter(|&&x| x % 2 == 0)
.cloned()
.collect();
let doubled: Vec = even_numbers
.iter()
.map(|&x| x * 2)
.collect();
let sum2: i32 = doubled.iter().sum();
println!("Same result, less efficient: {}", sum2);
}
Expected Output:
Sum of doubled even numbers: 500500
Same result, less efficient: 500500
Common Loop Patterns
Loop Until Success
fn main() {
let mut attempts = 0;
let success_threshold = 3;
let result = loop {
attempts += 1;
println!("Attempt {}", attempts);
// Simulate operation that might fail
if attempts >= success_threshold {
break Ok(format!("Success after {} attempts", attempts));
} else if attempts >= 5 {
break Err("Failed after maximum attempts".to_string());
}
println!("Operation failed, retrying...");
};
match result {
Ok(msg) => println!("{}", msg),
Err(err) => println!("Error: {}", err),
}
}
Expected Output:
Attempt 1
Operation failed, retrying...
Attempt 2
Operation failed, retrying...
Attempt 3
Success after 3 attempts
Best Practices
Choosing the Right Loop Type
- for loops: When iterating over collections or known ranges
- while loops: When looping while a condition is true
- loop: For infinite loops or when you need to return a value
Performance Tips
- Prefer iterator methods over index-based loops
- Use lazy iterator adaptors instead of collecting intermediate results
- Consider
break
with early return values - Use appropriate borrowing in for loops (
&
,&mut
, or ownership)
Common Pitfalls
- Infinite loops: Always ensure loop termination conditions
- Off-by-one errors: Be careful with ranges (
..
vs..=
) - Borrowing issues: Understand when to use
&
,&mut
, or take ownership - Nested loop labels: Use descriptive label names for clarity
Checks for Understanding
- What's the difference between
1..5
and1..=5
? - How do you return a value from a
loop
? - When would you use
continue
instead ofbreak
? - How do loop labels help with nested loops?
- What's the difference between
for x in vec
andfor x in &vec
?
Answers
1..5
excludes 5 (1,2,3,4),1..=5
includes 5 (1,2,3,4,5)- Use
break value;
where value is what you want to return continue
skips the rest of current iteration;break
exits the entire loop- Labels allow you to break or continue outer loops from inner loops
for x in vec
takes ownership and moves each element;for x in &vec
borrows each element