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.