Introduction to While Loops in Rust
While loops are fundamental control flow structures that repeatedly execute a block of code as long as a condition remains true. Rust's while loops combine simplicity with safety, providing clear syntax for conditional iteration while leveraging the language's strong type system and borrowing rules. Understanding while loops is essential for writing flexible and efficient Rust code.
Key Concepts
- Condition-based: Executes while a boolean condition is true
- Pre-test loop: Condition checked before each iteration
- Flexible conditions: Any expression that evaluates to bool
- Pattern integration: Works with
while letfor pattern matching - Control flow: Supports
break,continue, and loop labels
1. Basic While Loop Syntax
Simple While Loop
fn main() { // Basic while loop let mut counter = 0; while counter < 5 { println!("Counter: {}", counter); counter += 1; } // While loop with complex condition let mut x = 10; let mut y = 0; while x > 0 && y < 5 { println!("x: {}, y: {}", x, y); x -= 2; y += 1; } } Infinite Loops (While True)
fn main() { // Infinite loop with break condition let mut counter = 0; while true { println!("Iteration: {}", counter); counter += 1; if counter >= 5 { break; } } // More idiomatic: loop for infinite, while for conditions let mut counter = 0; loop { println!("Loop iteration: {}", counter); counter += 1; if counter >= 5 { break; } } } 2. While Loop with Complex Conditions
Compound Conditions
fn main() { let mut i = 0; let mut j = 10; // Multiple conditions with logical operators while i < 5 && j > 5 { println!("i: {}, j: {}", i, j); i += 1; j -= 1; } // OR condition let mut found = false; let mut attempts = 0; while !found && attempts < 10 { attempts += 1; found = simulate_search(); // Some function that might find something println!("Attempt {}: {}", attempts, if found { "Found!" } else { "Not found" }); } // Complex boolean expressions let mut value = 0; let max_value = 100; let threshold = 50; let flag = true; while value < max_value && (value < threshold || flag) { println!("Processing value: {}", value); value += 10; } } fn simulate_search() -> bool { // Simulate random search rand::random::<u8>() % 5 == 0 } While with Function Calls
fn main() { let mut data = vec![1, 2, 3, 4, 5]; // Condition using function while !data.is_empty() { let value = data.pop().unwrap(); println!("Popped: {}", value); } // While with method calls let mut text = String::from("Hello, World!"); while !text.is_empty() { println!("Text length: {}", text.len()); text.pop(); // Remove last character } // While with external state let mut resource = Some(create_resource()); while let Some(r) = resource.as_ref() { if r.is_ready() { break; } std::thread::sleep(std::time::Duration::from_millis(100)); } } fn create_resource() -> Resource { Resource { ready: false } } struct Resource { ready: bool, } impl Resource { fn is_ready(&self) -> bool { self.ready } } 3. While Let
Basic While Let
fn main() { // While let with Option let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); while let Some(top) = stack.pop() { println!("Popped: {}", top); } // While let with iterator let mut iter = (0..5).into_iter(); while let Some(value) = iter.next() { println!("Iterator value: {}", value); } // While let with Result let mut results = vec![Ok(1), Ok(2), Err("error"), Ok(3)]; while let Some(Ok(value)) = results.pop() { println!("Success: {}", value); } } While Let with Custom Types
enum Message { Text(String), Number(i32), Quit, } fn main() { let mut messages = vec![ Message::Text(String::from("Hello")), Message::Number(42), Message::Text(String::from("World")), Message::Quit, Message::Number(100), ]; // Process until Quit while let Some(msg) = messages.pop() { match msg { Message::Text(t) => println!("Text: {}", t), Message::Number(n) => println!("Number: {}", n), Message::Quit => { println!("Quit message received, stopping"); break; } } } // Process only Text messages let mut messages2 = vec![ Message::Text(String::from("Hello")), Message::Number(42), Message::Text(String::from("World")), ]; while let Some(Message::Text(text)) = messages2.pop() { println!("Processing text: {}", text); } } While Let with Destructuring
fn main() { // Destructuring tuples let mut pairs = vec![(1, 2), (3, 4), (5, 6)]; while let Some((x, y)) = pairs.pop() { println!("Pair: ({}, {})", x, y); } // Destructuring structs struct Point { x: i32, y: i32, } let mut points = vec![ Point { x: 10, y: 20 }, Point { x: 30, y: 40 }, Point { x: 50, y: 60 }, ]; while let Some(Point { x, y }) = points.pop() { println!("Point: ({}, {})", x, y); } // Destructuring with guards let mut numbers = vec![Some(1), Some(2), None, Some(3)]; while let Some(Some(num)) = numbers.pop() { if num > 1 { println!("Number > 1: {}", num); } } } 4. Loop Control
Break and Continue
fn main() { // Break - exit loop early let mut i = 0; while i < 10 { if i == 5 { println!("Breaking at {}", i); break; } println!("i = {}", i); i += 1; } // Continue - skip to next iteration let mut j = 0; while j < 10 { j += 1; if j % 2 == 0 { continue; // Skip even numbers } println!("Odd: {}", j); } // Break with value (using loop instead) let mut k = 0; let result = loop { k += 1; if k == 10 { break k * 2; } }; println!("Loop result: {}", result); } Loop Labels
fn main() { // Labeled break for nested loops let mut i = 0; 'outer: while i < 3 { let mut j = 0; while j < 3 { if i == 1 && j == 1 { println!("Breaking outer at i={}, j={}", i, j); break 'outer; } println!("i: {}, j: {}", i, j); j += 1; } i += 1; } // Labeled continue let mut i = 0; 'outer2: while i < 3 { let mut j = 0; while j < 3 { if i == 1 && j == 1 { println!("Continuing outer at i={}, j={}", i, j); i += 1; continue 'outer2; } println!("i: {}, j: {}", i, j); j += 1; } i += 1; } } 5. While Loops with Collections
Iterating with Index
fn main() { // Array iteration with index let arr = [10, 20, 30, 40, 50]; let mut index = 0; while index < arr.len() { println!("arr[{}] = {}", index, arr[index]); index += 1; } // Vector iteration with index let vec = vec!["apple", "banana", "orange"]; let mut i = 0; while i < vec.len() { println!("Fruit {}: {}", i, vec[i]); i += 1; } // 2D array traversal let matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; let mut row = 0; while row < matrix.len() { let mut col = 0; while col < matrix[row].len() { print!("{} ", matrix[row][col]); col += 1; } println!(); row += 1; } } Modifying Collections
fn main() { // Modifying while iterating let mut numbers = vec![1, 2, 3, 4, 5]; let mut i = 0; while i < numbers.len() { numbers[i] *= 2; // Double each number i += 1; } println!("Doubled: {:?}", numbers); // Removing elements while iterating let mut numbers2 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let mut i = 0; while i < numbers2.len() { if numbers2[i] % 2 == 0 { numbers2.remove(i); // Remove even numbers } else { i += 1; } } println!("After removing evens: {:?}", numbers2); // Better approach: retain let mut numbers3 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; numbers3.retain(|&x| x % 2 != 0); println!("Using retain: {:?}", numbers3); } Queue Processing
use std::collections::VecDeque; fn main() { // Simple queue processing let mut queue = VecDeque::new(); queue.push_back(1); queue.push_back(2); queue.push_back(3); queue.push_back(4); queue.push_back(5); while let Some(item) = queue.pop_front() { println!("Processing: {}", item); // Add more work based on processed item if item == 3 { queue.push_back(6); queue.push_back(7); } } // Priority queue simulation use std::collections::BinaryHeap; let mut heap = BinaryHeap::new(); heap.push(3); heap.push(1); heap.push(4); heap.push(2); heap.push(5); while let Some(item) = heap.pop() { println!("Processing highest priority: {}", item); } } 6. Error Handling in While Loops
While with Result
use std::io; use std::io::Write; fn main() { // Reading until valid input let mut input = String::new(); let mut valid_number = None; while valid_number.is_none() { print!("Enter a number: "); io::stdout().flush().unwrap(); input.clear(); match io::stdin().read_line(&mut input) { Ok(_) => match input.trim().parse::<i32>() { Ok(num) => { valid_number = Some(num); println!("Got valid number: {}", num); } Err(_) => println!("Invalid number, try again"), }, Err(e) => { println!("IO error: {}, try again", e); } } } // Processing with error recovery let data = vec!["1", "2", "three", "4", "five", "6"]; let mut processed = Vec::new(); let mut index = 0; while index < data.len() { match data[index].parse::<i32>() { Ok(num) => processed.push(num), Err(e) => { println!("Error at index {}: {} - skipping", index, e); // Continue processing } } index += 1; } println!("Processed numbers: {:?}", processed); } While with Custom Error Types
#[derive(Debug)] enum ProcessingError { InvalidData(String), Timeout, ConnectionLost, } fn process_with_retry() -> Result<(), ProcessingError> { let max_retries = 3; let mut retries = 0; while retries < max_retries { match perform_operation() { Ok(_) => return Ok(()), Err(e) => { println!("Attempt {} failed: {:?}", retries + 1, e); retries += 1; if retries == max_retries { return Err(e); } // Wait before retry std::thread::sleep(std::time::Duration::from_millis(100)); } } } Ok(()) } fn perform_operation() -> Result<(), ProcessingError> { // Simulate random failure if rand::random::<u8>() % 3 == 0 { Ok(()) } else { Err(ProcessingError::ConnectionLost) } } fn main() { match process_with_retry() { Ok(()) => println!("Operation successful"), Err(e) => println!("Operation failed after retries: {:?}", e), } } 7. While Loops with External Resources
File Processing
use std::fs::File; use std::io::{self, BufRead, BufReader, Write}; fn process_file_lines(filename: &str) -> io::Result<()> { let file = File::open(filename)?; let reader = BufReader::new(file); let mut lines = reader.lines(); let mut line_count = 0; while let Some(line) = lines.next() { let line = line?; line_count += 1; println!("Line {}: {}", line_count, line); if line_count >= 10 { println!("Reached 10 lines, stopping"); break; } } Ok(()) } fn write_with_retry(filename: &str, data: &[u8]) -> io::Result<()> { let max_retries = 3; let mut retries = 0; while retries < max_retries { match File::create(filename) { Ok(mut file) => { file.write_all(data)?; file.sync_all()?; return Ok(()); } Err(e) => { println!("Write attempt {} failed: {}", retries + 1, e); retries += 1; if retries == max_retries { return Err(e); } std::thread::sleep(std::time::Duration::from_millis(100)); } } } Ok(()) } fn main() -> io::Result<()> { // Process file lines let _ = process_file_lines("example.txt"); // Write with retry write_with_retry("output.txt", b"Hello, World!")?; Ok(()) } Network Operations
use std::net::TcpStream; use std::io::{Read, Write}; use std::time::Duration; fn connect_with_timeout(addr: &str, timeout_secs: u64) -> Option<TcpStream> { let start = std::time::Instant::now(); while start.elapsed() < Duration::from_secs(timeout_secs) { match TcpStream::connect(addr) { Ok(stream) => { println!("Connected to {}", addr); return Some(stream); } Err(e) => { println!("Connection attempt failed: {}", e); std::thread::sleep(Duration::from_millis(500)); } } } println!("Connection timeout after {} seconds", timeout_secs); None } fn read_from_stream(stream: &mut TcpStream) -> io::Result<Vec<u8>> { let mut buffer = Vec::new(); let mut chunk = [0; 1024]; // Read until connection closes or timeout let timeout = Duration::from_secs(5); let start = std::time::Instant::now(); while start.elapsed() < timeout { match stream.read(&mut chunk) { Ok(0) => break, // Connection closed Ok(n) => buffer.extend_from_slice(&chunk[..n]), Err(e) => { if e.kind() == io::ErrorKind::WouldBlock { // No data available, continue waiting std::thread::sleep(Duration::from_millis(100)); continue; } return Err(e); } } } Ok(buffer) } fn main() { if let Some(mut stream) = connect_with_timeout("127.0.0.1:8080", 5) { match read_from_stream(&mut stream) { Ok(data) => println!("Received {} bytes", data.len()), Err(e) => println!("Error reading: {}", e), } } } 8. Performance Considerations
Loop Optimizations
fn main() { // Bad: Repeated function calls in condition let data = vec![1, 2, 3, 4, 5]; let mut i = 0; while i < expensive_len_calculation(&data) { // Called each iteration println!("{}", data[i]); i += 1; } // Good: Cache the length let len = expensive_len_calculation(&data); let mut i = 0; while i < len { println!("{}", data[i]); i += 1; } // Bad: Complex condition each iteration let mut counter = 0; let threshold = 100; let max_iterations = 1000; let special_value = 50; while counter < max_iterations && counter * 2 < threshold * 3 && counter != special_value { counter += 1; } // Good: Simplify condition let mut counter = 0; while counter < max_iterations { if counter * 2 >= threshold * 3 || counter == special_value { break; } counter += 1; } } fn expensive_len_calculation<T>(_data: &[T]) -> usize { // Simulate expensive computation std::thread::sleep(std::time::Duration::from_micros(10)); _data.len() } Minimizing Allocations
fn main() { // Bad: Allocating inside loop let mut results = Vec::new(); let mut i = 0; while i < 100 { let mut temp = Vec::new(); // Allocated each iteration for j in 0..10 { temp.push(i * j); } results.push(temp); i += 1; } // Good: Reuse allocation let mut results = Vec::new(); let mut temp = Vec::with_capacity(10); let mut i = 0; while i < 100 { temp.clear(); // Reuse allocation for j in 0..10 { temp.push(i * j); } results.push(temp.clone()); // Still need to clone or move i += 1; } // Better: Avoid inner allocation entirely let mut results = Vec::new(); let mut i = 0; while i < 100 { let mut inner = Vec::with_capacity(10); for j in 0..10 { inner.push(i * j); } results.push(inner); // Move ownership i += 1; } } 9. Common Patterns and Idioms
Polling Pattern
use std::time::{Duration, Instant}; fn poll_until_ready<F>(mut check: F, timeout: Duration) -> bool where F: FnMut() -> bool { let start = Instant::now(); while start.elapsed() < timeout { if check() { return true; } std::thread::sleep(Duration::from_millis(100)); } false } fn main() { // Poll for condition let ready = poll_until_ready( || { // Check if resource is ready rand::random::<u8>() % 10 == 0 }, Duration::from_secs(5), ); if ready { println!("Resource ready!"); } else { println!("Timeout waiting for resource"); } // Polling with state let mut attempts = 0; let max_attempts = 10; while attempts < max_attempts { match try_operation() { Ok(result) => { println!("Operation succeeded: {}", result); break; } Err(e) => { attempts += 1; println!("Attempt {} failed: {}", attempts, e); if attempts == max_attempts { println!("Max attempts reached"); } else { std::thread::sleep(Duration::from_millis(200)); } } } } } fn try_operation() -> Result<String, &'static str> { if rand::random::<u8>() % 3 == 0 { Ok("Success".to_string()) } else { Err("Temporary failure") } } State Machine Pattern
#[derive(Debug, PartialEq)] enum ConnectionState { Disconnected, Connecting, Connected, Error, } fn run_connection_machine() { let mut state = ConnectionState::Disconnected; let mut retries = 0; const MAX_RETRIES: u32 = 3; while state != ConnectionState::Connected && retries < MAX_RETRIES { match state { ConnectionState::Disconnected => { println!("Starting connection..."); state = ConnectionState::Connecting; } ConnectionState::Connecting => { println!("Connecting..."); if rand::random::<u8>() % 2 == 0 { println!("Connected successfully!"); state = ConnectionState::Connected; } else { println!("Connection failed"); retries += 1; state = ConnectionState::Error; } } ConnectionState::Error => { if retries < MAX_RETRIES { println!("Retrying... (attempt {}/{})", retries, MAX_RETRIES); state = ConnectionState::Disconnected; } } ConnectionState::Connected => { // Already connected } } std::thread::sleep(std::time::Duration::from_millis(100)); } match state { ConnectionState::Connected => println!("Machine finished: Connected"), _ => println!("Machine finished: Failed after {} retries", retries), } } fn main() { run_connection_machine(); } 10. While Loops vs Other Loops
While vs Loop
fn main() { // While - when condition is checked at start let mut i = 0; while i < 5 { println!("While: {}", i); i += 1; } // Loop - when you need infinite loop or break with value let mut i = 0; let result = loop { if i >= 5 { break i * 2; } i += 1; }; println!("Loop result: {}", result); // While vs Loop with condition let mut i = 0; // While is more readable for simple conditions while i < 5 { i += 1; } // Loop can be used but is less clear let mut i = 0; loop { if i >= 5 { break; } i += 1; } } While vs For
fn main() { let numbers = [1, 2, 3, 4, 5]; // For - when iterating over collections for &num in &numbers { println!("For: {}", num); } // While - when you need index-based access with conditions let mut i = 0; while i < numbers.len() && numbers[i] < 4 { println!("While: {}", numbers[i]); i += 1; } // For with enumerate can sometimes replace while for (i, &num) in numbers.iter().enumerate() { if num >= 4 { break; } println!("For with enumerate: {}", num); } // While is better for non-iterative conditions let mut x = 100; while x > 0 { x /= 2; println!("Halving: {}", x); } } 11. Advanced Techniques
While with Channels
use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); // Producer thread thread::spawn(move || { for i in 0..10 { tx.send(i).unwrap(); thread::sleep(Duration::from_millis(100)); } }); // Consumer with while let while let Ok(value) = rx.recv() { println!("Received: {}", value); if value == 5 { println!("Got value 5, stopping"); break; } } // Multiple producers let (tx1, rx1) = mpsc::channel(); let tx2 = tx1.clone(); thread::spawn(move || { for i in 0..5 { tx1.send(format!("Thread1: {}", i)).unwrap(); thread::sleep(Duration::from_millis(50)); } }); thread::spawn(move || { for i in 0..5 { tx2.send(format!("Thread2: {}", i)).unwrap(); thread::sleep(Duration::from_millis(30)); } }); // Receive from both channels until both close while let Ok(msg) = rx1.recv_timeout(Duration::from_secs(1)) { println!("Got: {}", msg); } } While with Atomic Operations
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; fn main() { let running = Arc::new(AtomicBool::new(true)); let counter = Arc::new(std::sync::atomic::AtomicUsize::new(0)); // Worker threads let mut handles = vec![]; for id in 0..3 { let running = running.clone(); let counter = counter.clone(); handles.push(thread::spawn(move || { while running.load(Ordering::Relaxed) { // Do work let value = counter.fetch_add(1, Ordering::SeqCst); println!("Thread {} incremented to {}", id, value + 1); thread::sleep(Duration::from_millis(10)); } println!("Thread {} stopped", id); })); } // Main thread thread::sleep(Duration::from_millis(100)); running.store(false, Ordering::Relaxed); for handle in handles { handle.join().unwrap(); } println!("Final count: {}", counter.load(Ordering::SeqCst)); } 12. Debugging and Testing
Debugging While Loops
fn main() { // Adding debug output let mut i = 0; let target = 42; let mut found = false; while !found && i < 100 { // Debug print println!("DEBUG: iteration {}, checking {}", i, i * 2); if i * 2 == target { println!("Found target at iteration {}", i); found = true; } i += 1; } // Using assert in loops let mut sum = 0; let mut i = 0; while i < 10 { sum += i; i += 1; // Runtime assertion assert!(sum >= 0, "Sum should never be negative"); } println!("Final sum: {}", sum); } Testing While Loops
#[cfg(test)] mod tests { #[test] fn test_while_loop_behavior() { let mut count = 0; let mut sum = 0; while count < 5 { sum += count; count += 1; } assert_eq!(count, 5); assert_eq!(sum, 10); // 0+1+2+3+4 = 10 } #[test] fn test_while_let() { let mut stack = vec![1, 2, 3, 4, 5]; let mut popped = Vec::new(); while let Some(value) = stack.pop() { popped.push(value); } assert_eq!(popped, vec![5, 4, 3, 2, 1]); assert!(stack.is_empty()); } #[test] fn test_loop_break_condition() { let mut i = 0; let mut result = 0; while i < 10 { i += 1; if i == 5 { break; } result += i; } // Should sum: 1+2+3+4 = 10 assert_eq!(result, 10); } #[test] fn test_nested_while() { let mut outer_count = 0; let mut total = 0; while outer_count < 3 { let mut inner_count = 0; while inner_count < 3 { total += 1; inner_count += 1; } outer_count += 1; } assert_eq!(total, 9); // 3x3 iterations } } fn main() { println!("Run `cargo test` to run the tests"); } Conclusion
Rust's while loops provide flexible and safe conditional iteration:
Key Takeaways
- Condition-based: Execute while a boolean condition is true
- Pattern matching:
while letfor elegant pattern-based iteration - Control flow: Support for
break,continue, and loop labels - Safety: No off-by-one errors with proper condition design
- Flexibility: Can work with any boolean expression
When to Use While Loops
| Situation | Recommended Loop | Reason |
|---|---|---|
| Unknown number of iterations | while | Condition-based |
| Iterating over collection | for | More idiomatic |
| Pattern-based extraction | while let | Cleaner than loop + match |
| Infinite loop | loop | More explicit |
| Need break value | loop | Can return value |
Best Practices
- Keep conditions simple - Complex conditions are hard to read
- Avoid infinite loops - Ensure condition will eventually become false
- Use
while letfor pattern matching scenarios - Consider
forloops when iterating over collections - Cache expensive computations used in conditions
- Be careful with modifying collections while iterating
- Use loop labels for nested loops clarity
Common Pitfalls
- Forgetting to update loop variables
- Off-by-one errors in conditions
- Infinite loops due to never-changing conditions
- Modifying collections while iterating with indices
- Complex conditions that are hard to understand
While loops in Rust provide a familiar yet safe way to implement conditional iteration, with additional features like pattern matching that make them even more powerful and expressive.