google-site-verification: google61fe8ba583a51912.html
Complete Guide to Rust For Loops

Introduction to For Loops in Rust

For loops are fundamental control flow structures used for iterating over collections, ranges, and any type that implements the Iterator trait. Rust's for loops are designed to be safe, expressive, and efficient, leveraging the language's ownership system and iterator abstractions. Understanding for loops is crucial for writing idiomatic and performant Rust code.

Key Concepts

  • Iterator-based: For loops work with any type implementing IntoIterator
  • Ownership awareness: Control over borrowing vs consuming
  • Zero-cost abstractions: Iterators compile to efficient loop code
  • Pattern matching: Destructuring in loop variables
  • Range expressions: Concise syntax for numeric sequences

1. Basic For Loop Syntax

Simple Range Iteration

fn main() { // Basic range (exclusive end) for i in 0..5 { println!("i = {}", i); // Prints 0, 1, 2, 3, 4 } // Inclusive range (..=) for i in 0..=5 { println!("i = {}", i); // Prints 0, 1, 2, 3, 4, 5 } // Reverse range for i in (0..5).rev() { println!("i = {}", i); // Prints 4, 3, 2, 1, 0 } // Step by (using step_by) for i in (0..=10).step_by(2) { println!("Even: {}", i); // Prints 0, 2, 4, 6, 8, 10 } }

For Loop with Collections

fn main() { // Arrays let arr = [1, 2, 3, 4, 5]; for element in arr { println!("Element: {}", element); } // Vectors let vec = vec!["apple", "banana", "orange"]; for fruit in vec { println!("Fruit: {}", fruit); } // println!("{:?}", vec); // Error: vec moved // Slices let slice = &[1, 2, 3, 4, 5]; for num in slice { println!("Number: {}", num); // num is &i32 } // Strings (as chars) let text = "hello"; for c in text.chars() { println!("Char: {}", c); } // Strings (as bytes) for b in text.bytes() { println!("Byte: {}", b); } }

2. Iterating Over References

Borrowing in For Loops

fn main() { let numbers = vec![1, 2, 3, 4, 5]; // Immutable borrow (default for Vec) for num in &numbers { println!("num: {}", num); // num is &i32 } println!("Still have numbers: {:?}", numbers); // Explicitly borrow for num in numbers.iter() { println!("num: {}", num); // num is &i32 } println!("Still have numbers: {:?}", numbers); // Mutable borrow let mut mutable_numbers = vec![1, 2, 3, 4, 5]; for num in &mut mutable_numbers { *num *= 2; // Modify in place } println!("Modified: {:?}", mutable_numbers); // Into_iter consumes for num in numbers.into_iter() { println!("num: {}", num); // num is i32 (owned) } // println!("{:?}", numbers); // Error: numbers moved }

Iterating with Enumerate

fn main() { let fruits = vec!["apple", "banana", "orange"]; // Get index and value for (index, fruit) in fruits.iter().enumerate() { println!("{}: {}", index, fruit); } // With mutable values let mut scores = vec![10, 20, 30]; for (i, score) in scores.iter_mut().enumerate() { *score += i as i32 * 5; } println!("Modified scores: {:?}", scores); // Custom starting index for (i, fruit) in fruits.iter().enumerate() { println!("{}: {}", i + 1, fruit); // Start at 1 } }

3. Pattern Matching in For Loops

Destructuring in Loops

fn main() { // Tuples in vector let points = vec![(1, 2), (3, 4), (5, 6)]; for (x, y) in points { println!("Point: ({}, {})", x, y); } // Structs struct Person { name: String, age: u32, } let people = vec![ Person { name: "Alice".to_string(), age: 30 }, Person { name: "Bob".to_string(), age: 25 }, ]; for Person { name, age } in people { println!("{} is {} years old", name, age); } // Enums enum Color { RGB(u8, u8, u8), Hex(String), } let colors = vec![ Color::RGB(255, 0, 0), Color::Hex("#00FF00".to_string()), ]; for color in colors { match color { Color::RGB(r, g, b) => println!("RGB: {},{},{}", r, g, b), Color::Hex(h) => println!("Hex: {}", h), } } }

Ignoring Values

fn main() { // Ignore index let items = vec!["a", "b", "c"]; for (_, item) in items.iter().enumerate() { println!("Item: {}", item); } // Ignore some tuple elements let records = vec![(1, "Alice", 30), (2, "Bob", 25)]; for (_, name, _) in records { println!("Name: {}", name); } // Ignore with pattern let options = vec![Some(1), None, Some(3)]; for opt in options { if let Some(val) = opt { println!("Value: {}", val); } // None values are ignored } }

4. Advanced Iteration Techniques

Chaining Iterators

fn main() { // Chain two iterators let v1 = vec![1, 2, 3]; let v2 = vec![4, 5, 6]; for i in v1.iter().chain(v2.iter()) { println!("Chained: {}", i); } // Zip iterators 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); } // Zip with different lengths let short = vec![1, 2]; let long = vec![1, 2, 3, 4]; for pair in short.iter().zip(long.iter()) { println!("Pair: {:?}", pair); // Stops when shortest ends } // Cycle (repeat forever) let pattern = vec!["red", "green", "blue"]; for color in pattern.iter().cycle().take(7) { println!("Color: {}", color); } }

Filtering and Transforming

fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; // Filter for even in numbers.iter().filter(|&&x| x % 2 == 0) { println!("Even: {}", even); } // Map (transform) for squared in numbers.iter().map(|&x| x * x) { println!("Squared: {}", squared); } // Filter map (combine filter and map) let words = vec!["1", "2", "three", "4", "five"]; for num in words.iter().filter_map(|w| w.parse::<i32>().ok()) { println!("Parsed number: {}", num); } // Flat map let nested = vec![vec![1, 2], vec![3, 4], vec![5, 6]]; for num in nested.iter().flatten() { println!("Flattened: {}", num); } }

Skipping and Taking

fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Take first n elements for num in numbers.iter().take(3) { println!("Take: {}", num); // 1, 2, 3 } // Skip first n elements for num in numbers.iter().skip(5) { println!("Skip: {}", num); // 6, 7, 8, 9, 10 } // Skip while condition true for num in numbers.iter().skip_while(|&&x| x < 5) { println!("Skip while: {}", num); // 5, 6, 7, 8, 9, 10 } // Take while condition true for num in numbers.iter().take_while(|&&x| x < 5) { println!("Take while: {}", num); // 1, 2, 3, 4 } // Step by for num in numbers.iter().step_by(2) { println!("Step by 2: {}", num); // 1, 3, 5, 7, 9 } }

5. Loop Control Flow

Break and Continue

fn main() { // Break - exit loop early for i in 0..10 { if i == 5 { break; } println!("Break example: {}", i); // Prints 0-4 } // Continue - skip to next iteration for i in 0..10 { if i % 2 == 0 { continue; // Skip even numbers } println!("Continue example: {}", i); // Prints odd numbers } // Break with value (in loop, not for loop) let mut i = 0; let result = loop { i += 1; if i == 10 { break i * 2; // Return value from loop } }; println!("Loop result: {}", result); }

Loop Labels

fn main() { // Labeled break for nested loops 'outer: for i in 1..=3 { for j in 1..=3 { if i == 2 && j == 2 { break 'outer; // Breaks outer loop } println!("i: {}, j: {}", i, j); } } // Labeled continue 'outer2: for i in 1..=3 { for j in 1..=3 { if i == 2 && j == 2 { continue 'outer2; // Continues outer loop } println!("i: {}, j: {}", i, j); } } // Practical example: searching in 2D let matrix = vec![ vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9], ]; let target = 5; let mut found_at = None; 'search: for (i, row) in matrix.iter().enumerate() { for (j, &value) in row.iter().enumerate() { if value == target { found_at = Some((i, j)); break 'search; } } } println!("Found at: {:?}", found_at); }

6. For Loops with Ranges

Range Types

fn main() { // Range (start..end) - exclusive let range = 1..5; for i in range { println!("Range: {}", i); // 1, 2, 3, 4 } // Range inclusive (start..=end) let inclusive = 1..=5; for i in inclusive { println!("Inclusive: {}", i); // 1, 2, 3, 4, 5 } // Range from (start..) let from = 3..; for i in from.take(3) { println!("From: {}", i); // 3, 4, 5 } // Range to (..end) let to = ..5; for i in to.start.unwrap_or(0)..5 { println!("To: {}", i); // 0, 1, 2, 3, 4 } // Full range (..) let full = ..; // Can't iterate directly, needs context }

Custom Range Iteration

fn main() { // Characters range for c in 'a'..='z' { print!("{} ", c); if c == 'e' { break; } // Prevent too much output } println!(); // Step by with characters (via conversion) for c in ('a'..='z').step_by(2) { print!("{} ", c); if c == 'e' { break; } } println!(); // Reverse ranges for i in (1..=5).rev() { println!("Reverse: {}", i); // 5, 4, 3, 2, 1 } // Combining ranges with other iterators let sum: i32 = (1..=10).sum(); println!("Sum 1 to 10: {}", sum); let product: i32 = (1..=5).product(); println!("Product 1 to 5: {}", product); }

7. Performance Considerations

Avoiding Allocations

fn main() { // Bad: Creates intermediate Vec let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect(); for num in doubled { println!("Doubled: {}", num); } // Good: Process without allocation for num in numbers.iter().map(|&x| x * 2) { println!("Doubled: {}", num); } // Bad: Collecting when not needed let even: Vec<_> = numbers.iter().filter(|&&x| x % 2 == 0).collect(); for num in even { println!("Even: {}", num); } // Good: Filter without collecting for num in numbers.iter().filter(|&&x| x % 2 == 0) { println!("Even: {}", num); } }

Loop Optimizations

fn main() { // C-style loop (sometimes faster for simple counters) let mut sum = 0; for i in 0..1000000 { sum += i; } println!("Sum: {}", sum); // While loop equivalent let mut i = 0; let mut sum2 = 0; while i < 1000000 { sum2 += i; i += 1; } println!("Sum2: {}", sum2); // Using iterators with optimizations let data = vec![1, 2, 3, 4, 5]; // These are usually optimized to similar code // Version 1: for loop let mut sum3 = 0; for &x in &data { sum3 += x; } // Version 2: iterator let sum4: i32 = data.iter().sum(); println!("Sum3: {}, Sum4: {}", sum3, sum4); }

Cache-Friendly Loops

fn main() { // Cache-friendly: sequential access let matrix = vec![vec![0; 1000]; 1000]; // Good: Row-major order let start = std::time::Instant::now(); for i in 0..1000 { for j in 0..1000 { let _ = matrix[i][j]; } } println!("Row-major: {:?}", start.elapsed()); // Bad: Column-major order (cache misses) let start = std::time::Instant::now(); for j in 0..1000 { for i in 0..1000 { let _ = matrix[i][j]; } } println!("Column-major: {:?}", start.elapsed()); }

8. For Loops with Custom Types

Implementing IntoIterator

struct Range { start: i32, end: i32, } impl IntoIterator for Range { type Item = i32; type IntoIter = RangeIterator; fn into_iter(self) -> Self::IntoIter { RangeIterator { current: self.start, end: self.end, } } } struct RangeIterator { current: i32, end: i32, } impl Iterator for RangeIterator { type Item = i32; fn next(&mut self) -> Option<Self::Item> { if self.current < self.end { let value = self.current; self.current += 1; Some(value) } else { None } } } fn main() { let range = Range { start: 0, end: 5 }; for i in range { println!("Custom range: {}", i); // 0, 1, 2, 3, 4 } }

Iterating Over Collections

struct ShoppingCart { items: Vec<String>, } impl ShoppingCart { fn new() -> Self { ShoppingCart { items: Vec::new() } } fn add_item(&mut self, item: String) { self.items.push(item); } // Immutable iterator fn iter(&self) -> impl Iterator<Item = &String> { self.items.iter() } // Mutable iterator fn iter_mut(&mut self) -> impl Iterator<Item = &mut String> { self.items.iter_mut() } } fn main() { let mut cart = ShoppingCart::new(); cart.add_item("Apple".to_string()); cart.add_item("Banana".to_string()); cart.add_item("Orange".to_string()); // Immutable iteration for item in cart.iter() { println!("Item: {}", item); } // Mutable iteration for item in cart.iter_mut() { *item = format!("Fresh {}", item); } println!("Updated cart:"); for item in cart.iter() { println!("Item: {}", item); } }

9. Parallel Iteration

Using Rayon for Parallel Processing

// Add to Cargo.toml: // [dependencies] // rayon = "1.7" use rayon::prelude::*; fn main() { let numbers: Vec<i32> = (0..1000).collect(); // Sequential iteration let start = std::time::Instant::now(); let squares_seq: Vec<i32> = numbers.iter() .map(|&x| x * x) .collect(); println!("Sequential: {:?}", start.elapsed()); // Parallel iteration let start = std::time::Instant::now(); let squares_par: Vec<i32> = numbers.par_iter() .map(|&x| x * x) .collect(); println!("Parallel: {:?}", start.elapsed()); // Parallel with filter let even_squares: Vec<i32> = numbers.par_iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * x) .collect(); println!("Even squares count: {}", even_squares.len()); // Parallel sum let sum: i32 = numbers.par_iter().sum(); println!("Parallel sum: {}", sum); }

10. For Loop Patterns

Common Patterns

fn main() { // Finding maximum let numbers = vec![3, 7, 2, 9, 5]; let mut max = numbers[0]; for &num in &numbers { if num > max { max = num; } } println!("Max: {}", max); // Using iterator method let max = numbers.iter().max().unwrap(); println!("Max using iterator: {}", max); // Finding element let target = 7; let mut found_index = None; for (i, &num) in numbers.iter().enumerate() { if num == target { found_index = Some(i); break; } } println!("Found at: {:?}", found_index); // Using iterator position let found = numbers.iter().position(|&x| x == target); println!("Found using position: {:?}", found); // Accumulating let sum: i32 = numbers.iter().sum(); println!("Sum: {}", sum); // Building new collection let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect(); println!("Doubled: {:?}", doubled); }

Nested Loops Patterns

fn main() { // Multiplication table for i in 1..=5 { for j in 1..=5 { print!("{:4}", i * j); } println!(); } // Triangle pattern for i in 1..=5 { for _ in 0..i { print!("* "); } println!(); } // 2D array traversal let matrix = vec![ vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9], ]; // Sum of all elements let mut sum = 0; for row in &matrix { for &val in row { sum += val; } } println!("Sum of all elements: {}", sum); // Diagonal sum let mut diagonal_sum = 0; for i in 0..matrix.len() { diagonal_sum += matrix[i][i]; } println!("Diagonal sum: {}", diagonal_sum); }

11. Error Handling in Loops

Continuing on Error

use std::num::ParseIntError; fn main() { let inputs = vec!["42", "24", "abc", "12", "xyz"]; let mut valid_numbers = Vec::new(); // Skip invalid inputs for input in inputs { match input.parse::<i32>() { Ok(num) => valid_numbers.push(num), Err(_) => continue, // Skip invalid } } println!("Valid numbers: {:?}", valid_numbers); // Using filter_map (more idiomatic) let inputs2 = vec!["42", "24", "abc", "12", "xyz"]; let numbers: Vec<i32> = inputs2.iter() .filter_map(|s| s.parse().ok()) .collect(); println!("Filtered numbers: {:?}", numbers); // Collect results with errors let results: Vec<Result<i32, ParseIntError>> = inputs2.iter() .map(|s| s.parse()) .collect(); println!("Results: {:?}", results); }

Breaking on Error

use std::fs::File; use std::io::{self, BufRead}; fn read_lines_until_error() -> io::Result<()> { let file = File::open("example.txt")?; let reader = io::BufReader::new(file); for line in reader.lines() { match line { Ok(content) => { if content.is_empty() { break; // Stop at empty line } println!("Line: {}", content); } Err(e) => return Err(e), // Propagate error } } Ok(()) } fn main() { match read_lines_until_error() { Ok(()) => println!("File processed successfully"), Err(e) => println!("Error reading file: {}", e), } }

12. Performance Best Practices

Loop Optimizations

fn main() { // Bad: Repeated length calculation let data = vec![1, 2, 3, 4, 5]; for i in 0..data.len() { println!("{}", data[i]); // Bounds check each iteration } // Good: Iterate directly for &item in &data { println!("{}", item); // No bounds checks } // Bad: Collecting unnecessarily let squares: Vec<i32> = data.iter().map(|&x| x * x).collect(); for square in squares { println!("{}", square); } // Good: Chain operations without collecting for square in data.iter().map(|&x| x * x) { println!("{}", square); } // Bad: Multiple passes let sum: i32 = data.iter().sum(); let count = data.len(); let avg = sum as f64 / count as f64; // Good: Single pass (if needed for complex operations) let mut sum = 0; let mut count = 0; for &x in &data { sum += x; count += 1; } let avg = sum as f64 / count as f64; }

Memory Layout Considerations

fn main() { // Struct of arrays vs array of structs // Bad: Array of structs (poor cache locality for field access) struct Point { x: f32, y: f32, } let points: Vec<Point> = (0..1000).map(|i| Point { x: i as f32, y: i as f32, }).collect(); // Accessing all x coordinates causes cache misses let mut x_sum = 0.0; for point in &points { x_sum += point.x; } // Good: Struct of arrays (better cache locality) struct Points { x: Vec<f32>, y: Vec<f32>, } let points2 = Points { x: (0..1000).map(|i| i as f32).collect(), y: (0..1000).map(|i| i as f32).collect(), }; // All x coordinates are contiguous in memory let mut x_sum2 = 0.0; for &x in &points2.x { x_sum2 += x; } }

13. Advanced For Loop Techniques

Infinite Iterators

fn main() { // Infinite iterator (take to limit) let mut counter = 0; for i in std::iter::repeat(5).take(5) { println!("Repeat: {}", i); } // Cycle through finite sequence let colors = vec!["red", "green", "blue"]; for color in colors.iter().cycle().take(7) { println!("Cycle: {}", color); } // Generate infinite sequence for i in std::iter::successors(Some(0), |&n| Some(n + 1)).take(5) { println!("Successor: {}", i); } // Fibonacci with successors let fib = std::iter::successors(Some((0, 1)), |&(a, b)| Some((b, a + b))) .map(|(a, _)| a) .take(10) .collect::<Vec<_>>(); println!("Fibonacci: {:?}", fib); }

Custom Iterator Combinators

fn main() { let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Windows (sliding window) for window in data.windows(3) { println!("Window: {:?}", window); } // Chunks for chunk in data.chunks(3) { println!("Chunk: {:?}", chunk); } // Pairwise (using zip) for (a, b) in data.iter().zip(data.iter().skip(1)) { println!("Pair: ({}, {})", a, b); } // Group by adjacent use std::iter::Peekable; let numbers = vec![1, 1, 2, 2, 2, 3, 4, 4, 5]; let mut iter = numbers.iter().peekable(); while let Some(&first) = iter.next() { let mut count = 1; while let Some(&&next) = iter.peek() { if next == first { count += 1; iter.next(); } else { break; } } println!("{} appears {} times", first, count); } }

Conclusion

Rust's for loops and iterators provide powerful, safe, and efficient ways to process sequences of data:

Key Takeaways

  1. Iterator-based: For loops work with any type implementing IntoIterator
  2. Ownership control: Choose between iter(), iter_mut(), and into_iter()
  3. Zero-cost abstractions: Iterators compile to efficient code
  4. Expressiveness: Chain combinators for complex transformations
  5. Safety: Bounds checking and borrow checker prevent errors
  6. Flexibility: Work with ranges, collections, custom types

Common Patterns

PatternDescriptionExample
Basic iterationfor item in collectionfor i in 0..10
With index.enumerate()for (i, item) in items.iter().enumerate()
Filtering.filter()for even in numbers.iter().filter(|&|x % 2 == 0)
Transformation.map()for squared in numbers.iter().map(|&x| x * x)
Early exitbreak/continueif condition { break; }
Nested loopsLoop labels'outer: for ... { break 'outer; }

Best Practices

  1. Prefer iterators over indexing when possible
  2. Use enumerate() when you need both index and value
  3. Chain combinators for readable transformations
  4. Consider performance with large datasets
  5. Use break and continue judiciously
  6. Leverage filter_map() for filtering + transformation
  7. Profile parallel iterators for CPU-bound tasks

Performance Guidelines

  • Direct iteration is usually fastest
  • Avoid unnecessary collect() calls
  • Use step_by() for skipping elements
  • Consider cache locality for large data structures
  • Profile before optimizing

Rust's for loops and iterators provide a powerful, expressive, and safe way to work with sequences of data, making code both readable and efficient.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper