Introduction to Loops in Rust
Loops are fundamental control flow structures that allow you to execute a block of code multiple times. Rust provides several types of loops, each designed for different scenarios, with a strong emphasis on safety and expressiveness. Rust's loop system includes powerful features like loop labels, break with values, and integration with iterators.
Key Concepts
- Loop Types:
loop,while,forfor different use cases - Control Flow:
break,continuefor loop manipulation - Loop Labels: Named loops for nested control
- Pattern Matching: Integration with
while letand pattern matching - Iterator Integration:
forloops work with any iterator
1. The loop Keyword
Infinite Loops
fn main() { // Basic infinite loop let mut counter = 0; loop { counter += 1; println!("Iteration: {}", counter); if counter >= 5 { break; // Exit the loop } } // Loop with condition inside let mut attempts = 0; loop { attempts += 1; println!("Attempt {}", attempts); // Simulate some operation let success = attempts == 3; if success { println!("Success on attempt {}!", attempts); break; } if attempts >= 5 { println!("Failed after {} attempts", attempts); break; } } } Returning Values from Loops
fn main() { // Loop can return a value with break let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; // Return 20 from the loop } }; println!("Result from loop: {}", result); // 20 // More complex example let mut guess = 0; let secret_number = 42; let message = loop { guess += 1; if guess == secret_number { break format!("Found it in {} guesses!", guess); } if guess > 100 { break "Couldn't find it...".to_string(); } }; println!("{}", message); } Nested Loops
fn main() { // Simple nested loops for i in 1..=3 { for j in 1..=3 { println!("i = {}, j = {}", i, j); } } // Nested with conditions let matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; 'outer: for i in 0..3 { for j in 0..3 { if matrix[i][j] == 5 { println!("Found 5 at position ({}, {})", i, j); break 'outer; // Break outer loop } println!("Checking ({}, {}) = {}", i, j, matrix[i][j]); } } } 2. Loop Labels
Labeled Loops for Precise Control
fn main() { // Basic loop labels 'outer: loop { println!("Entered outer loop"); 'inner: loop { println!("Entered inner loop"); // break 'outer; // This would exit both loops break 'inner; // This only exits inner loop } println!("Still in outer loop"); break 'outer; } // Nested loops with labels and conditions let mut count = 0; 'counting_up: loop { println!("Count = {}", count); let mut remaining = 10; 'inner: loop { if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {}", count); } Labeled Break and Continue
fn main() { // Using labels with break and continue 'outer: for i in 1..=5 { println!("Outer iteration: {}", i); 'inner: for j in 1..=5 { if i == 3 && j == 3 { println!("Breaking outer loop at i={}, j={}", i, j); break 'outer; } if j == 2 { println!(" Continuing inner loop at j={}", j); continue 'inner; } println!(" Inner iteration: {}", j); } } // More complex example let data = vec![ vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9], ]; 'search: for (i, row) in data.iter().enumerate() { for (j, &value) in row.iter().enumerate() { if value == 5 { println!("Found 5 at [{}, {}]", i, j); break 'search; } if value % 2 == 0 { println!("Even number {} at [{}, {}], skipping row", value, i, j); continue 'search; // Continue to next row } println!("Checking [{}, {}] = {}", i, j, value); } } } 3. The while Loop
Basic While Loops
fn main() { // Simple while loop let mut number = 3; while number != 0 { println!("{}!", number); number -= 1; } println!("LIFTOFF!!!"); // While with complex condition let mut x = 0; let mut y = 10; while x < y { println!("x = {}, y = {}", x, y); x += 1; y -= 1; } // While with boolean flag let mut running = true; let mut counter = 0; while running { counter += 1; println!("Running... ({})", counter); if counter >= 5 { running = false; } } } While with Pattern Matching
fn main() { // while let for pattern matching let mut optional = Some(0); while let Some(i) = optional { if i > 9 { println!("Greater than 9, quitting!"); optional = None; } else { println!("`i` is `{:?}`. Try again.", i); optional = Some(i + 1); } } // More practical example let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("Popped: {}", top); } // Working with iterators let mut iter = 1..=5; while let Some(value) = iter.next() { println!("Next value: {}", value); } } 4. The for Loop
Basic For Loops with Ranges
fn main() { // For loop with range (exclusive) for i in 0..5 { println!("i = {}", i); // 0, 1, 2, 3, 4 } // For loop with inclusive range for i in 1..=5 { println!("i = {}", i); // 1, 2, 3, 4, 5 } // Reverse range for i in (1..=5).rev() { println!("Reverse: {}", i); // 5, 4, 3, 2, 1 } // Step by (using step_by) for i in (0..=10).step_by(2) { println!("Even: {}", i); // 0, 2, 4, 6, 8, 10 } // Using variables in ranges let start = 5; let end = 10; for i in start..=end { println!("Variable range: {}", i); } } For Loops with Collections
fn main() { // Arrays let arr = [10, 20, 30, 40, 50]; for element in arr.iter() { println!("Array element: {}", element); } // Vectors let vec = vec!["apple", "banana", "cherry"]; for fruit in &vec { println!("Fruit: {}", fruit); } // With index using enumerate for (index, fruit) in vec.iter().enumerate() { println!("Fruit {}: {}", index, fruit); } // Mutable iteration let mut numbers = vec![1, 2, 3, 4, 5]; for num in &mut numbers { *num *= 2; } println!("Doubled: {:?}", numbers); // HashMaps use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert("Alice", 100); scores.insert("Bob", 90); scores.insert("Charlie", 95); for (name, score) in &scores { println!("{}: {}", name, score); } } For Loops with Custom Iterators
struct Counter { count: u32, max: u32, } impl Counter { fn new(max: u32) -> Counter { Counter { count: 0, max } } } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { self.count += 1; if self.count <= self.max { Some(self.count) } else { None } } } fn main() { // Using custom iterator let counter = Counter::new(5); for number in counter { println!("Count: {}", number); } // Chaining iterator methods let sum: u32 = Counter::new(10) .filter(|&x| x % 2 == 0) .map(|x| x * x) .sum(); println!("Sum of squares of evens: {}", sum); } 5. Loop Control Statements
Break Statement
fn main() { // Basic break for i in 1..=10 { if i == 5 { println!("Breaking at {}", i); break; } println!("i = {}", i); } // Break with value in loop let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("Break with value: {}", result); // Break in nested loops with labels 'outer: for i in 1..=3 { for j in 1..=3 { if i == 2 && j == 2 { println!("Breaking outer at ({}, {})", i, j); break 'outer; } println!("({}, {})", i, j); } } } Continue Statement
fn main() { // Basic continue for i in 1..=10 { if i % 2 == 0 { continue; // Skip even numbers } println!("Odd: {}", i); } // Continue with label 'outer: for i in 1..=5 { for j in 1..=5 { if i == j { println!("Skipping diagonal at ({}, {})", i, j); continue 'outer; // Continue outer loop } println!("({}, {})", i, j); } } // Practical example: process only valid items let items = vec![1, -2, 3, -4, 5, -6]; for item in items { if item < 0 { println!("Skipping negative: {}", item); continue; } println!("Processing positive: {}", item); } } Combining Break and Continue
fn main() { // Complex flow with break and continue let data = vec![ vec![1, 2, -1, 4], vec![5, -2, 7, 8], vec![9, 10, 11, -3], ]; 'outer: for (row_idx, row) in data.iter().enumerate() { println!("Processing row {}", row_idx); for (col_idx, &value) in row.iter().enumerate() { if value < 0 { println!("Found negative at ({}, {}), skipping row", row_idx, col_idx); continue 'outer; } if value == 10 { println!("Found 10 at ({}, {}), stopping completely", row_idx, col_idx); break 'outer; } println!(" Value at ({}, {}): {}", row_idx, col_idx, value); } } } 6. The while let Construct
Pattern Matching with While Let
fn main() { // Basic while let with Option let mut optional = Some(0); while let Some(x) = optional { if x > 5 { println!("Reached {}, stopping", x); optional = None; } else { println!("x is {}", x); optional = Some(x + 1); } } // With Result let mut results: Vec<Result<i32, &str>> = vec![ Ok(1), Ok(2), Err("error"), Ok(3), ]; while let Some(Ok(value)) = results.pop() { println!("Got value: {}", value); } // With custom enum enum MyEnum { Foo(i32), Bar(String), Baz, } let mut values = vec![ MyEnum::Foo(42), MyEnum::Bar("hello".to_string()), MyEnum::Baz, MyEnum::Foo(100), ]; while let Some(MyEnum::Foo(x)) = values.pop() { println!("Found Foo: {}", x); } } While Let with References
fn main() { // Working with references let mut data = vec![Some(1), Some(2), None, Some(3)]; while let Some(Some(value)) = data.pop() { println!("Popped value: {}", value); } // With mutable references let mut numbers = vec![1, 2, 3, 4, 5]; let mut iter = numbers.iter_mut(); while let Some(num) = iter.next() { *num *= 2; } println!("Doubled: {:?}", numbers); // Destructuring in while let let mut pairs = vec![(1, "one"), (2, "two"), (3, "three")]; while let Some((num, name)) = pairs.pop() { println!("{}: {}", name, num); } } 7. Loop Performance Considerations
Efficient Looping
fn main() { // Avoid unnecessary allocations let data = vec![1, 2, 3, 4, 5]; // Good: Iterate by reference for item in &data { println!("{}", item); } // Bad: Would consume the vector // for item in data { // data moved here // println!("{}", item); // } // Still can use data println!("Data still available: {:?}", data); // Pre-allocate when size known let mut results = Vec::with_capacity(1000); for i in 0..1000 { results.push(i * 2); } println!("Results length: {}", results.len()); } Loop Optimizations
fn main() { // Compiler optimizations // Simple loops are often optimized // This loop might be unrolled or vectorized let mut sum = 0; for i in 0..1000 { sum += i; } println!("Sum: {}", sum); // Using iterators can be as fast as manual loops let data: Vec<i32> = (0..1000).collect(); // Iterator version (often optimized well) let sum_iter: i32 = data.iter().sum(); println!("Iterator sum: {}", sum_iter); // Manual loop version let mut sum_manual = 0; for i in 0..data.len() { sum_manual += data[i]; } println!("Manual sum: {}", sum_manual); } 8. Common Loop Patterns
Accumulation Patterns
fn main() { // Sum accumulation let numbers = [1, 2, 3, 4, 5]; let mut sum = 0; for num in numbers.iter() { sum += num; } println!("Sum: {}", sum); // Product accumulation let mut product = 1; for num in 1..=5 { product *= num; } println!("Factorial of 5: {}", product); // Building collections let mut squares = Vec::new(); for i in 1..=10 { squares.push(i * i); } println!("Squares: {:?}", squares); // Finding max/min let values = [45, 23, 78, 12, 56, 89, 34]; let mut max = values[0]; for &value in values.iter().skip(1) { if value > max { max = value; } } println!("Maximum: {}", max); } Filtering Patterns
fn main() { let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Filter even numbers let mut evens = Vec::new(); for &num in numbers.iter() { if num % 2 == 0 { evens.push(num); } } println!("Evens: {:?}", evens); // Filter and transform let mut doubled_odds = Vec::new(); for &num in numbers.iter() { if num % 2 != 0 { doubled_odds.push(num * 2); } } println!("Doubled odds: {:?}", doubled_odds); // Counting matches let mut count = 0; for &num in numbers.iter() { if num > 5 { count += 1; } } println!("Numbers > 5: {}", count); } Searching Patterns
fn main() { let data = [4, 8, 15, 16, 23, 42]; // Linear search let target = 23; let mut found_index = None; for (i, &value) in data.iter().enumerate() { if value == target { found_index = Some(i); break; } } match found_index { Some(i) => println!("Found {} at index {}", target, i), None => println!("{} not found", target), } // Search with condition let threshold = 20; let mut found_value = None; for &value in data.iter() { if value > threshold { found_value = Some(value); break; } } println!("First value > {}: {:?}", threshold, found_value); } 9. Infinite Loops and Termination
Safe Infinite Loops
use std::thread; use std::time::Duration; fn main() { // Server-like infinite loop let mut request_count = 0; loop { request_count += 1; println!("Processing request #{}", request_count); // Simulate work thread::sleep(Duration::from_millis(100)); // Condition to break (in real server, this might never happen) if request_count >= 10 { println!("Shutting down after {} requests", request_count); break; } } // Event loop pattern let mut events = vec!["click", "keypress", "resize", "close"]; while let Some(event) = events.pop() { println!("Handling event: {}", event); if event == "close" { println!("Close event received, exiting event loop"); break; } } // Polling loop let mut attempts = 0; let max_attempts = 5; loop { attempts += 1; println!("Polling attempt {}", attempts); // Simulate checking a condition let ready = attempts >= 3; if ready { println!("Resource ready!"); break; } if attempts >= max_attempts { println!("Timeout: resource not ready"); break; } thread::sleep(Duration::from_millis(500)); } } Preventing Infinite Loops
fn main() { // Always ensure loops have a termination condition let mut counter = 0; let max_iterations = 1000; while some_condition() { counter += 1; // Safety check if counter > max_iterations { println!("Loop exceeded max iterations, breaking"); break; } // Normal loop logic println!("Iteration {}", counter); } // Using iterators instead of manual loops when possible let numbers = 0..; // This is safe because we limit with take() for num in numbers.take(10) { println!("Number: {}", num); } } fn some_condition() -> bool { // Simulate condition that might always be true true } 10. Advanced Loop Techniques
Loop with State
fn main() { // State machine in a loop #[derive(Debug, PartialEq)] enum State { Start, Processing, Validating, Completed, Error, } let mut state = State::Start; let mut data = 0; loop { match state { State::Start => { println!("Starting process..."); data = 42; state = State::Processing; } State::Processing => { println!("Processing data: {}", data); data *= 2; state = State::Validating; } State::Validating => { println!("Validating result: {}", data); if data > 0 { state = State::Completed; } else { state = State::Error; } } State::Completed => { println!("Process completed with result: {}", data); break; } State::Error => { println!("Error occurred!"); break; } } } } Generator Pattern
struct Fibonacci { current: u64, next: u64, } impl Fibonacci { fn new() -> Self { Fibonacci { current: 0, next: 1 } } } impl Iterator for Fibonacci { type Item = u64; fn next(&mut self) -> Option<Self::Item> { let new_next = self.current + self.next; self.current = self.next; self.next = new_next; Some(self.current) } } fn main() { // Using generator pattern let fib = Fibonacci::new(); for (i, num) in fib.take(10).enumerate() { println!("Fibonacci {}: {}", i + 1, num); } // Custom range generator struct StepRange { current: i32, end: i32, step: i32, } impl StepRange { fn new(start: i32, end: i32, step: i32) -> Self { StepRange { current: start, end, step, } } } impl Iterator for StepRange { type Item = i32; fn next(&mut self) -> Option<Self::Item> { if self.current <= self.end { let result = self.current; self.current += self.step; Some(result) } else { None } } } let range = StepRange::new(0, 10, 2); for num in range { println!("Step: {}", num); } } Parallel Processing Patterns
use std::thread; fn main() { // Simple parallel processing with threads let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let chunk_size = data.len() / 4; let mut handles = vec![]; for chunk in data.chunks(chunk_size) { let chunk = chunk.to_vec(); handles.push(thread::spawn(move || { let sum: i32 = chunk.iter().sum(); println!("Chunk sum: {}", sum); sum })); } let mut total = 0; for handle in handles { total += handle.join().unwrap(); } println!("Total sum: {}", total); // Rayon parallel iterators (if feature enabled) // use rayon::prelude::*; // let sum: i32 = data.par_iter().sum(); // println!("Parallel sum: {}", sum); } 11. Error Handling in Loops
Loops with Results
use std::fs::File; use std::io::{self, Write}; fn main() -> io::Result<()> { // Loop with Result handling let mut attempts = 0; let max_attempts = 3; let result = loop { attempts += 1; match try_operation() { Ok(value) => break Ok(value), Err(e) => { println!("Attempt {} failed: {}", attempts, e); if attempts >= max_attempts { break Err(e); } } } }; match result { Ok(value) => println!("Success: {}", value), Err(e) => println!("All attempts failed: {}", e), } // Collecting Results let operations = vec!["file1.txt", "file2.txt", "file3.txt"]; let mut successful = Vec::new(); let mut errors = Vec::new(); for filename in operations { match File::create(filename) { Ok(file) => successful.push(file), Err(e) => errors.push((filename, e)), } } println!("Successful: {}, Failed: {}", successful.len(), errors.len()); Ok(()) } fn try_operation() -> Result<i32, &'static str> { // Simulate operation that might fail static COUNTER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0); let attempt = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); if attempt < 2 { Err("Temporary failure") } else { Ok(42) } } Loop with Option
fn main() { // Processing until None let mut data = Some(0); while let Some(x) = data { println!("Processing: {}", x); if x >= 5 { data = None; } else { data = Some(x + 1); } } // Collecting Options let items = vec![Some(1), None, Some(2), Some(3), None, Some(4)]; let mut valid_items = Vec::new(); for item in items { if let Some(value) = item { valid_items.push(value); } else { println!("Skipping None"); continue; } } println!("Valid items: {:?}", valid_items); // Find first Some let values = vec![None, None, Some(42), None, Some(100)]; let mut found = None; for value in values { if value.is_some() { found = value; break; } } println!("First Some: {:?}", found); } 12. Loop Macros and Utilities
Custom Loop Macros
macro_rules! repeat { ($n:expr, $body:block) => { for _ in 0..$n { $body } }; } macro_rules! while_not { ($cond:expr, $body:block) => { while !$cond { $body } }; } macro_rules! loop_with_index { ($iter:expr, $body:expr) => { for (index, item) in $iter.enumerate() { $body(index, item); } }; } fn main() { // Using custom macros repeat!(5, { println!("Repeating!"); }); let mut x = 0; while_not!(x >= 5, { println!("x = {}", x); x += 1; }); let data = vec!["a", "b", "c", "d"]; loop_with_index!(data.iter(), |i, v| { println!("Index {}: {}", i, v); }); } Utility Functions for Loops
// Generic loop utilities fn times<F>(n: usize, mut f: F) where F: FnMut(usize), { for i in 0..n { f(i); } } fn repeat_until<T, F>(mut f: F) -> T where F: FnMut() -> Option<T>, { loop { if let Some(result) = f() { return result; } } } fn for_each_with_state<T, S, F>(items: &[T], mut state: S, mut f: F) where F: FnMut(&T, &mut S), { for item in items { f(item, &mut state); } } fn main() { // Using utility functions times(5, |i| { println!("Time {}: Hello!", i); }); let mut counter = 0; let result = repeat_until(|| { counter += 1; println!("Attempt {}", counter); if counter >= 5 { Some(counter) } else { None } }); println!("Result after {} attempts", result); let numbers = vec![1, 2, 3, 4, 5]; let mut sum = 0; for_each_with_state(&numbers, &mut sum, |&num, acc| { **acc += num; }); println!("Sum: {}", sum); } 13. Best Practices and Common Patterns
Idiomatic Loop Usage
fn main() { // Prefer for loops over while for iterating collections let items = vec![1, 2, 3, 4, 5]; // Good for item in &items { println!("{}", item); } // Less idiomatic let mut i = 0; while i < items.len() { println!("{}", items[i]); i += 1; } // Use iterators for transformations let squares: Vec<i32> = items.iter().map(|&x| x * x).collect(); println!("Squares: {:?}", squares); // Use enumerate when index is needed for (i, item) in items.iter().enumerate() { println!("items[{}] = {}", i, item); } // Use zip for parallel iteration let names = vec!["Alice", "Bob", "Charlie"]; let ages = vec![30, 25, 35]; for (name, age) in names.iter().zip(ages.iter()) { println!("{} is {} years old", name, age); } } Loop Performance Tips
fn main() { // Avoid unnecessary bounds checks let data = vec![1, 2, 3, 4, 5]; // Good: Iterator avoids bounds checks for &x in &data { println!("{}", x); } // Less efficient: Manual indexing with bounds checks for i in 0..data.len() { println!("{}", data[i]); // Bounds check each iteration } // Pre-allocate when size known let mut results = Vec::with_capacity(1000); for i in 0..1000 { results.push(i * 2); } // Use references to avoid copying let large_data = vec![String::from("hello"); 100]; for s in &large_data { // Borrows, doesn't copy println!("{}", s); } // For large data, iterating by value consumes // for s in large_data { // large_data moved here // println!("{}", s); // } // Can't use large_data after this } Readability Patterns
fn main() { // Use meaningful variable names let mut total = 0; // Good for student_score in &[85, 92, 78, 95, 88] { total += student_score; } // Use early continue for clarity for number in 1..=20 { // Skip odd numbers early if number % 2 != 0 { continue; } // Process even numbers println!("Even: {}", number); } // Complex condition with named boolean let should_process = true; let data = vec![1, 2, 3, 4, 5]; for value in data { if !should_process { break; } // Complex processing here println!("Processing: {}", value); } // Using loop labels for clarity 'find_first: for i in 0..10 { for j in 0..10 { if i * j == 42 { println!("Found at ({}, {})", i, j); break 'find_first; } } } } Conclusion
Rust's loop system provides powerful and safe iteration constructs:
Key Takeaways
- loop: Infinite loops with break/continue control
- while: Condition-based looping
- for: Iterator-based looping (most common)
- while let: Pattern matching loops
- Loop Labels: Named loops for nested control
- Break with Values: Return values from loops
- Iterator Integration: Seamless work with iterators
Best Practices
- Prefer
forloops with iterators for collections - Use
while letfor pattern-based looping - Use loop labels for clarity in nested loops
- Break with values when you need a result
- Consider performance with large datasets
- Use meaningful names for loop variables
- Handle errors appropriately in loops
When to Use Each Loop Type
| Loop Type | Best Used For |
|---|---|
loop | Infinite loops, retry logic, servers |
while | Condition-based iteration |
for | Collection iteration, ranges |
while let | Pattern matching, Option/Result unwrapping |
Rust's loop constructs combine safety with expressiveness, making it easy to write correct and efficient iteration code.