Mastering Control Flow in Rust
In Rust, understanding control flow is crucial for writing efficient and safe code. The language provides powerful constructs such as if
expressions and loops to control the flow of operations based on conditions. Let's dive into these concepts to understand their usage and nuances.
if Expressions: Making Decisions in Rust
Basic if-else
In Rust, if
expressions are used to execute code based on conditions. An if
block must have a condition that evaluates to a boolean value (true
or false
). If the condition is true, the code inside the if
block is executed; otherwise, the code inside the else
block is executed.
Example:
fn main() {
let number = 42;
if number > 50 {
println!("Number is greater than 50");
} else {
println!("Number is less than or equal to 50");
}
}
It’s also worth noting that the condition in this code must be a bool. If the condition isn’t a bool, we’ll get an error.
Example:
fn main() {
let number = 3;
if number {
// mismatched types, expected `bool`, found integer
println!("number was three");
}
}
if-else if
ladder
Rust allows chaining multiple conditions using else if
, creating a hierarchical decision-making structure. The conditions are evaluated in order, and the corresponding block of code for the first true condition is executed.
Example:
fn main() {
let grade = 85;
if grade >= 90 {
println!("Grade A");
} else if grade >= 80 {
println!("Grade B");
} else if grade >= 70 {
println!("Grade C");
} else {
println!("Grade below C");
}
}
Using if
in Assignments
In Rust, if
expressions can be used in assignments, allowing you to assign values based on conditions. The value assigned is the result of the executed block based on the condition.
Example:
fn main() {
let is_even = if number % 2 == 0 { true } else { false };
println!("Is the number even? {}", is_even);
}
Note that the return values from each arm of the if
must be the same type, or we’ll get an error.
Example:
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
// `if` and `else` have incompatible types
// expected integer, found `&str`
println!("The value of number is: {number}");
}
Ternary Operator (Shorthand for Simple if-else
)
Although Rust does not have a traditional ternary operator, you can achieve similar functionality using a shorthand with if-else
expressions.
Example:
fn main() {
let number = 42;
let result = if number > 50 { "greater" } else { "less or equal" };
println!("Number is {}", result);
}
Repetition with Loops: Controlling Iteration in Rust
loop
The loop
keyword creates an infinite loop, which continues until explicitly interrupted using break
.
Example:
fn main() {
let mut count = 0;
loop {
println!("Count: {}", count);
count += 1;
if count == 5 {
break; // Exit the loop when count reaches 5
}
}
}
You can also use loop
to return a value out of the loop.
Example:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}"); // 20
}
loop
inside loop
:
You can create nested loops by placing one loop
inside another one. But in this case, break
and continue
will apply to the innermost loop at that point.
To avoid this problem, rust have something called loop labels
. Loop labels allow you to name loops, and then use the break
or continue
statements with a specific label to indicate which loop you want to affect.
Loop labels must begin with a single quote.
Example:
'outer: loop {
// Code inside the outer loop
'inner: loop {
// Code inside the inner loop
if some_condition {
break 'inner;
}
if another_condition {
break 'outer;
}
}
// Code after the inner loop
}
// Code after the labeled loops
while
The while
loop executes a block of code as long as a specified condition is true. It is useful when the number of iterations is not known beforehand.
Example:
fn main() {
let mut number = 1;
while number <= 5 {
println!("Number: {}", number);
number += 1;
}
}
for
The for
loop iterates over a range, a collection, or an iterable object, executing the block of code for each iteration. It is commonly used with arrays, ranges, and iterators.
Example with range:
fn main() {
for number in (1..=3).rev() {
// rev(), to reverse the range
println!("{number}!");
}
println!("LIFTOFF!!!"); // Output: 3 2 1 "LIFTOFF!!!"
}
Example with iterable object (e.g., array):
fn main() {
let numbers = [1, 2, 3, 4, 5];
for number in numbers.iter() {
println!("Number: {}", number);
}
}
The .iter()
method is used to create an iterator over the elements of the array numbers
. The loop then iterates over each element using this iterator. Using .iter()
, does not consume the array; it only borrows the elements. This means the array numbers
can still be used after the loop.
Example without iterator:
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
Here, the array a
is directly used in the loop. In this case, the loop consumes the array, and each iteration takes ownership
of each element of the array. If the elements of the array were of a type that does not implement the Copy
trait, this approach would move the elements out of the array, and the array a
would be unusable after the loop.
We will learn about the
Ownership
in the upcoming blogs.
Loop Control Statements
Rust provides break
and continue
statements to control the flow within loops. break
exits the loop, while continue
skips the rest of the current iteration and proceeds to the next one.
Example with break
and continue
:
fn main() {
for number in 1..=10 {
if number % 2 == 0 {
continue; // Skip even numbers
}
println!("Number: {}", number);
if number == 7 {
break; // Exit the loop when number is 7
}
}
}
Conclusion
Understanding control flow constructs such as if
expressions and loops is essential for writing efficient and robust Rust code. These constructs enable you to make decisions and control the flow of your programs, allowing for complex logic and iteration. Mastering these concepts will empower you to write clear, concise, and reliable Rust code.