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
- Iterator-based: For loops work with any type implementing
IntoIterator - Ownership control: Choose between
iter(),iter_mut(), andinto_iter() - Zero-cost abstractions: Iterators compile to efficient code
- Expressiveness: Chain combinators for complex transformations
- Safety: Bounds checking and borrow checker prevent errors
- Flexibility: Work with ranges, collections, custom types
Common Patterns
| Pattern | Description | Example |
|---|---|---|
| Basic iteration | for item in collection | for 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 exit | break/continue | if condition { break; } |
| Nested loops | Loop labels | 'outer: for ... { break 'outer; } |
Best Practices
- Prefer iterators over indexing when possible
- Use
enumerate()when you need both index and value - Chain combinators for readable transformations
- Consider performance with large datasets
- Use
breakandcontinuejudiciously - Leverage
filter_map()for filtering + transformation - 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.