Control Flow in Rust
Introduction
Control flow is how we direct the execution path of our program based on conditions and repetition. Rust provides several control flow constructs that are both powerful and safe.
Conditional Statements (if/else)
Basic if/else Syntax
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
> Important: Unlike many other languages, Rust has some distinct rules for conditionals:
> 1. Parentheses around conditions are optional
> 2. Curly braces {}
are mandatory, even for single-line blocks
> 3. Conditions must be boolean expressions
Multiple Conditions (else if)
fn main() {
let number = 12;
let description = if number > 0 {
"positive"
} else if number < 0 {
"negative"
} else {
"zero"
};
println!("The number is {}", description);
}
If Expressions
Rust's if
is an expression, meaning it can return values:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value is: {}", number);
}
> Note: Both branches must return the same type!
Loops
Rust provides three types of loops: loop
, while
, and for
.
1. loop - Infinite Loops
The loop
keyword creates an infinite loop that can be broken out of using break
:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Returns a value!
}
};
println!("The result is {}", result); // Prints: The result is 20
}
Key Features:
- Can run indefinitely
- Can break with a return value
- Useful for retry operations
- Good for cases where the exit condition is in the middle of the loop
2. while Loops
While loops continue as long as a condition is true:
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!");
}
Common Use Cases:
- Conditional iteration
- Reading input until a condition is met
- Processing data with an unknown size
3. for Loops
For loops are used to iterate over collections:
// Iterating over an array
fn main() {
let numbers = [10, 20, 30, 40, 50];
// Using iterator
for number in numbers.iter() {
println!("Value: {}", number);
}
// Using range
for i in 0..numbers.len() {
println!("numbers[{}] = {}", i, numbers[i]);
}
}
Key Features:
- Safe and efficient
- No index out of bounds errors
- Works with any iterable collection
- Can use ranges with
..
or..=
Advanced Control Flow Patterns
1. Breaking Outer Loops
You can break outer loops using labels:
'outer: loop {
loop {
if condition {
break 'outer; // Breaks the outer loop
}
}
}
2. Continuing Loops
The continue
keyword skips to the next iteration:
fn main() {
for number in 1..=5 {
if number % 2 == 0 {
continue; // Skip even numbers
}
println!("{}", number);
}
}
3. Conditional Compilation
#[cfg(target_os = "linux")]
fn linux_only() {
println!("This only runs on Linux!");
}
Best Practices
-
Choose the Right Loop
- Use
for
when iterating over collections - Use
while
when you need a condition - Use
loop
when you need to retry operations
- Use
-
Loop Safety
- Always ensure loops can terminate
- Use break conditions
- Consider using timeouts for potentially infinite loops
-
Performance Considerations
- Prefer
for
overwhile
when possible - Use iterators instead of index-based access
- Avoid unnecessary bounds checking
- Prefer
Common Pitfalls
- Infinite Loops
// Wrong
while true { // Use loop instead
// ...
}
// Correct
loop {
// ...
}
- Type Mismatch in If Expressions
// Wrong
let number = if condition { 5 } else { "six" }; // Error!
// Correct
let number = if condition { 5 } else { 6 };
- Forgetting Break Conditions
// Wrong
loop {
do_something();
// Oops, no break condition!
}
// Correct
loop {
do_something();
if should_stop() {
break;
}
}
Next Steps
After mastering control flow:
- Learn about pattern matching with
match
- Explore error handling with
Result
andOption
- Study iterators and closures
- Understand async control flow
Remember: Rust's control flow constructs are designed to be safe and prevent common programming errors. Understanding them well is crucial for writing reliable Rust programs.