Rust provides several mechanisms to control the execution flow of a program. While it shares common keywords like if, for, and while with other C-style languages, Rust introduces unique behaviors—such as expressions that return values—and replaces the traditional switch statement with the more powerful match construct.
Conditional Logic with if Expressions
In Rust, if is an expression rather than just a statement. This means it can return a value that can be assigned to a variable. Every block in an if expression must return the same type.
fn main() {
let threshold = 50;
let input_value = 75;
// Standard if/else usage
if input_value > threshold {
println!("Value is above the limit.");
} else {
println!("Value is within the limit.");
}
// Using if as an expression for assignment
let status = if input_value > 100 {
"High"
} else if input_value > 50 {
"Medium"
} else {
"Low"
};
println!("Current status: {}", status);
}
Repetition with Loops
Rust offers three types of loops: loop, while, and for.
The loop Keyword
The loop keyword creates an infinite loop. It is often used when you need to retry an operation until it succeeds. A unique feature of loop in Rust is its ability to return a value through the break statement.
fn main() {
let mut ticks = 0;
let result = loop {
ticks += 1;
if ticks == 10 {
break ticks * 2; // Returns a value from the loop
}
};
println!("Loop exited with value: {}", result);
}
Conditional Loops with while
The while loop executes as long as a specified condition remains true.
fn countdown_timer(start: i32) {
let mut current = start;
while current > 0 {
println!("{}...", current);
current -= 1;
}
println!("Liftoff!");
}
Iterating with for
The for loop is the most common way to iterate over collections or ranges in Rust. Its safer and more idiomatic than using a while loop with an index.
fn run_for_demos() {
// Iterating over a fixed array
let data_points = [10, 20, 30, 40];
for point in data_points {
println!("Data point: {}", point);
}
// Iterating over a range (exclusive of the upper bound)
for i in 1..5 {
println!("Step: {}", i);
}
// Iterating over a range (inclusive of the upper bound)
for i in 1..=5 {
println!("Inclusive Step: {}", i);
}
// Iterating in reverse
for i in (1..4).rev() {
println!("Countdown: {}", i);
}
}
Pattern Matching with match
Rust does not include a switch statement. Instead, it uses match, which allows you to compare a value against a series of patterns and execute code based on which pattern matches. Patterns can be literal values, variable names, wildcards, and many other things.
enum ProcessState {
Idle,
Running(u8),
Error(String),
}
fn handle_state(state: ProcessState) {
match state {
ProcessState::Idle => println!("System is waiting."),
ProcessState::Running(load) if load > 80 => {
println!("System is running under heavy load: {}%", load);
}
ProcessState::Running(load) => {
println!("System is running at {}% capacity.", load);
}
ProcessState::Error(msg) => {
println!("Critical error encountered: {}", msg);
}
}
}
One of the key strengths of match is exhaustiveness: the compiler ensures that all possible cases are handled, preventing bugs caused by unhandled states.
Recursion and Control Flow
Control flow structures are often combined with recursion. However, developers should be mindful of performance in recursive functions, such as those calculating Fibonacci sequences, as they can become computationally expensive without memoization.
fn compute_fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => compute_fibonacci(n - 1) + compute_fibonacci(n - 2),
}
}