Complete Guide to Rust While Loops

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 let for 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

  1. Condition-based: Execute while a boolean condition is true
  2. Pattern matching: while let for elegant pattern-based iteration
  3. Control flow: Support for break, continue, and loop labels
  4. Safety: No off-by-one errors with proper condition design
  5. Flexibility: Can work with any boolean expression

When to Use While Loops

SituationRecommended LoopReason
Unknown number of iterationswhileCondition-based
Iterating over collectionforMore idiomatic
Pattern-based extractionwhile letCleaner than loop + match
Infinite looploopMore explicit
Need break valueloopCan return value

Best Practices

  1. Keep conditions simple - Complex conditions are hard to read
  2. Avoid infinite loops - Ensure condition will eventually become false
  3. Use while let for pattern matching scenarios
  4. Consider for loops when iterating over collections
  5. Cache expensive computations used in conditions
  6. Be careful with modifying collections while iterating
  7. 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.

Leave a Reply

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


Macro Nepal Helper