// Function to create a formatted banner
fn banner(sep: &str, nchar: usize, message: &str) {
let sep = sep.repeat(nchar);
let message = format!("{:^width$}", message, width = nchar);
println!("\n{}\n{}\n{}", sep, message, sep);
}
Returning Values from Loops
In Rust, loops can be more than just control flow structures for repeated execution—they can also be used to compute and return values. This is made possible through the use of the break
statement with an associated value, which allows a loop to return a result upon termination. This feature enables more flexible and expressive looping constructs, particularly useful in scenarios where the loop's purpose is to find or compute a specific result.
Syntax
The syntax for returning a value from a loop involves initializing a variable with the loop
construct and using the break
statement to return a value:
#![allow(unused)] fn main() { let result = loop { // code to execute if condition { break value; } }; }
In this structure:
- loop: Initializes an infinite loop that will continue running until explicitly terminated.
- if condition { break value; }: Inside the loop, a condition is evaluated. When this condition is true, the break statement is executed with an associated value, which terminates the loop and returns the value as the result of the loop.
Benefits
- Using loops to return values can streamline your code by combining iteration and value computation into a single, concise construct. It eliminates the need for additional variables and state management outside the loop, leading to cleaner and more maintainable code. This approach is particularly powerful in scenarios such as searching, accumulating values, or any other context where the loop's purpose is to produce a single result.
Example
Consider the following example where we use a loop to find the first number greater than 10 that is divisible by both 2 and 3:
fn main() {
banner("*", 52, "Returning Values from a loop");
let mut num = 1;
let result = loop {
num += 1;
if num > 10 && num % 2 == 0 && num % 3 == 0 {
break num;
}
};
println!("The number is: {}", result);
println!("{}", "*".repeat(52));
}
main();
****************************************************
Returning Values from a loop
****************************************************
The number is: 12
****************************************************
Code in Details
Initialization:
let mut num = 1
: A mutable integer variablenum
is initialized to 1. This variable will be incremented in each iteration of the loop.
Infinite Loop:
let result = loop { ... }
: An infinite loop is initiated, which will run until it is terminated by abreak
statement.
Increment and Check:
num += 1
: The variablenum
is incremented by 1 in each iteration.if num > 10 && num % 2 == 0 && num % 3 == 0 { break num; }
: Inside the loop, a condition is evaluated. Ifnum
is greater than 10 and divisible by both 2 and 3, thebreak
statement is executed withnum
as its value. This terminates the loop and assignsnum
to the variableresult
.
Post-loop:
println!("The number is: {}", result);
: After the loop terminates, the value ofresult
is printed to the console, displaying the first number greater than 10 that meets the specified conditions.
Loop Labels and Nesting
In Rust, you can label loops and refer to these labels to control nested loops. This is particularly useful when you need to break or continue outer loops. When you have nested loops, the break
and continue
statements by default apply to the innermost loop. However, by using loop labels, you can specify which loop the break
or continue
statements should apply to. Loop labels must begin with a single quote.
Syntax
#![allow(unused)] fn main() { 'label: loop { // code for the outer loop loop { // code for the inner loop break 'label; // breaks the outer loop } } }
Structure Explanation
Loop Label:
'label: loop { ... }
: The outer loop is labeled with'label
. This label can be referenced later in the code to control the flow of the outer loop.
Outer Loop:
- The outer loop contains some code to execute and is identified by the label
'label
. The code within this loop will execute repeatedly unless explicitly interrupted by abreak
statement.
Inner Loop:
- Within the outer loop, there is an inner loop. The code within this loop will also execute repeatedly. By default, any
break
orcontinue
statements within this inner loop apply only to the inner loop.
Break Statement with Label:
break 'label;
: This statement breaks the outer loop labeled'label
. This allows the code to exit the outer loop from within the inner loop, demonstrating the control provided by loop labels.
Example with Loop Labels and Nesting
Let us consider the next example, where we break out of an outer loop using a label
fn main() {
banner("*", 52, "Breaking Loop by labels");
'outer: loop {
println!("Entered the outer loop");
'inner: loop {
println!("Entered the inner loop");
// Condition to break the outer loop
break 'outer;
}
println!("This point will never be reached");
}
println!("Exited the outer loop");
println!("{}", "*".repeat(52));
}
main();
****************************************************
Breaking Loop by labels
****************************************************
Entered the outer loop
Entered the inner loop
Exited the outer loop
****************************************************
Code in Details
Outer Loop Initialization:
'outer: loop { ... }
: The outer loop is labeled with'outer
. This label is used to control the flow of the outer loop from within the nested inner loop.
Entering the Outer Loop:
println!("Entered the outer loop");
: This statement is executed each time the outer loop begins its iteration.
Inner Loop Initialization:
'inner: loop { ... }
: Within the outer loop, an inner loop is defined and labeled with'inner
.
Entering the Inner Loop:
println!("Entered the inner loop");
: This statement is executed each time the inner loop begins its iteration.
Breaking the Outer Loop:
break 'outer;
: Inside the inner loop, this statement uses the loop label'outer
to break the outer loop. This terminates the outer loop immediately, skipping any remaining code inside both the inner and outer loops.
Unreachable Code in the Outer Loop:
println!("This point will never be reached");
: This statement is placed after the inner loop within the outer loop. However, it will never be executed because thebreak 'outer;
statement exits the outer loop before this point is reached.
Post-loop Execution:
println!("Exited the outer loop");
: After thebreak 'outer;
statement terminates the outer loop, this statement is executed, indicating that the program has exited the outer loop.
Another Example
This example is adapted from rust book, official documentation.
fn main() {
banner("*", 52, "Understanding nested loop and labels");
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
println!("{}", "*".repeat(52));
}
main();
****************************************************
Understanding nested loop and labels
****************************************************
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
****************************************************
Practical Example: Finding a Specific Value in a 2D Array
Let's consider a practical example where we have a 2D array (matrix) and we want to find a specific value. We will use nested loops to iterate through the array, and loop labels to break out of both loops once we find the value.
fn main() {
banner("*", 52, "Practical Example of Nested loop with labels");
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
let target = 5;
let mut found = false;
'outer: for (i, row) in matrix.iter().enumerate() {
for (j, &value) in row.iter().enumerate() {
if value == target {
println!("Found {} at position ({}, {})", target, i, j);
found = true;
break 'outer;
}
}
}
if !found {
println!("{} not found in the matrix", target);
}
println!("{}", "*".repeat(52));
}
main();
****************************************************
Practical Example of Nested loop with labels
****************************************************
Found 5 at position (1, 1)
****************************************************
Code in Details
Matrix Initialization:
let matrix = [...];
: A 2D array (matrix) is initialized with integers from 1 to 9.
Target Value:
let target = 5;
: The target value we are searching for in the matrix is set to 5.let mut found = false;
: A mutable boolean variablefound
is initialized to false. It will be set to true if the target value is found.
Outer Loop Initialization:
'outer: for (i, row) in matrix.iter().enumerate() { ... }
: The outer loop is labeled with'outer
and iterates over each row in the matrix. Theenumerate()
method is used to get both the index and the row.
Inner Loop Initialization:
for (j, &value) in row.iter().enumerate() { ... }
: The inner loop iterates over each value in the current row. Theenumerate()
method is used to get both the index and the value.
Condition Check and Break:
if value == target { ... }
: Inside the inner loop, this condition checks if the current value is equal to the target value.println!("Found {} at position ({}, {})", target, i, j);
: If the condition is met, a message is printed indicating the position of the target value.found = true;
: Thefound
variable is set to true.break 'outer;
: Thebreak 'outer;
statement is used to exit the outer loop immediately.
Post-loop Check:
if !found { ... }
: After the loops, this condition checks if the target value was not found.println!("{} not found in the matrix", target);
: If the target value was not found, a message is printed indicating that the value was not found in the matrix.
Practical Example 02: Image Processing
Let consider an example in image processing where we want to search for a specific color pattern in a 3D image represented as a matrix of RGB values. If we find the pattern, we break out of the nested loops.
fn main() {
// Example 3D image matrix (2x2 pixels for simplicity)
let image = [
[[255, 0, 0], [0, 255, 0]], // Row 1: [Red, Green]
[[0, 0, 255], [255, 255, 0]], // Row 2: [Blue, Yellow]
];
// Target pattern (Yellow)
let target = [255, 255, 0];
let mut found = false;
'outer: for (i, row) in image.iter().enumerate() {
for (j, pixel) in row.iter().enumerate() {
if pixel == &target {
println!("Found the pattern at position ({}, {})", i, j);
found = true;
break 'outer;
}
}
}
if !found {
println!("Pattern not found in the image");
}
}
main();
Found the pattern at position (1, 1)
Code in Details
Image Initialization:
#![allow(unused)] fn main() { let image = [ [[255, 0, 0], [0, 255, 0]], // Row 1: [Red, Green] [[0, 0, 255], [255, 255, 0]], // Row 2: [Blue, Yellow] ]; }
A 3D array (image) is initialized with RGB values representing the colors Red, Green, Blue, and Yellow.
Target Pattern:
#![allow(unused)] fn main() { let target = [255, 255, 0]; // Yellow let mut found = false; }
The target color pattern we are searching for is set to Yellow [255, 255, 0]. A mutable boolean variable found is initialized to false. It will be set to true if the target pattern is found.
Outer Loop Initialization:
#![allow(unused)] fn main() { 'outer: for (i, row) in image.iter().enumerate() { ... } }
The outer loop is labeled with 'outer and iterates over each row in the image matrix. The enumerate() method is used to get both the index and the row.
Inner Loop Initialization:
#![allow(unused)] fn main() { for (j, pixel) in row.iter().enumerate() { ... } }
Within the outer loop, an inner loop iterates over each pixel in the current row. The enumerate() method is used to get both the index and the pixel.
Condition Check and Break:
#![allow(unused)] fn main() { if pixel == &target { ... } }
Inside the inner loop, this condition checks if the current pixel matches the target pattern.
Print and Break:
#![allow(unused)] fn main() { println!("Found the pattern at position ({}, {})", i, j); found = true; break 'outer; }
If the condition is met, a message is printed indicating the position of the target pattern. The found variable is set to true, and the break 'outer; statement is used to exit the outer loop immediately.
Post-loop Check:
#![allow(unused)] fn main() { if !found { ... } }
After the loops, this condition checks if the target pattern was not found. If the target pattern was not found, a message is printed indicating that the pattern was not found in the image
Conclusion
In this section, we explored the more advanced features of Rust loops, specifically focusing on two crucial aspects: returning values from loops and utilizing loop labels to effectively manage nested loops.
We began by discussing how Rust's unique capability of allowing loops to return values using the break
statement with a value can greatly enhance the flexibility and control over loop executions. This feature allows for more expressive and powerful loop constructs, enabling developers to capture and utilize results directly from within the loop.
Next, we examined the use of loop labels to manage nested loops. By labeling loops with descriptive identifiers, we can precisely control which loop is affected by break
and continue
statements, thus avoiding confusion and ensuring the correct loop is targeted. This is particularly useful in complex scenarios involving multiple layers of nested loops, where direct control over the outer loops from within inner loops is necessary.