Loops
If you read the Control flow, you have already been introduced to loops. To recap, there are basically three ways to create a loop:
for
loop
while
for
loops
A super common way to repeat an action a number of times is with a for
loop:
fn main() { for i in 0..10 { print!("{} ", i) } println!(); }
Like many other development languages, Rust also supports custom step-sizes while iterating:
fn main() { for i in (0..10).step_by(2) { print!("{} ", i) } println!(); }
... or reverse traversal:
fn main() { for i in (0..10).rev() { print!("{} ", i) } println!(); }
loop
loops
For custom scenario's a loop
might be more suited. loop
loops are often combined with a break
statement to exit
the loop. As per the Rust book, it can be very useful to capture the result of a loop
in a variable.
fn main() { let mut i = 103; let result = loop { i += 1; if i % 25 == 0 { break i; } }; println!("{result} can be divided by 25"); }
Of course, you could just print the value of
i
in the above example. Pretend you didn't notice that.
inner loop
s
Rust supports labels for loops. This can be useful when you have nested loops, and you want to break out of the outer loop from the inner loop.
fn main() { 'outer: loop { println!("Entered the outer loop"); loop { println!("Entered the inner loop"); break 'outer; } println!("This point will never be reached"); } println!("Exited the outer loop"); }
while
loops
Conditional loops with while
might be used to achieve a similar result.
fn main() { let mut i = 103; while i % 25 != 0 { i += 1; } println!("{i} can be divided by 25"); }
Don't go crazy!
As you've seen, results of if
, match
and loop
blocks can be assigned to a variable. Although this is a very
powerful mechanism, don't go crazy on nesting these variations. The fact that something can be done, does not mean
it must be done. Someone needs to maintain and debug this code!
Imagine you need to work on something like the below:
fn main() { for i in &[0, 2, 4, 17, 29, 45, 102] { let age = *i; let description = match age { 0..=9 => { if age < 2 { "baby" } else { match age { 2..=3 => "toddler", _ => "child", } } } 10..=17 => "teenager", a => { if a < 30 { "young adult" } else { let mut i = a; loop { i += 1; if i == 65 { break "adult"; } else if i > 65 { break "elderly"; } } } } }; println!("{age} = {description}"); } }
Using variables in loops
Let's have a quick look at the below example. At first glance, this may seem a valid code, greeting someone 10 times.
fn greet(name: String) { println!("Welcome {name}"); } fn main() { let name = "Marcel".to_string(); for _i in 0..10 { greet(name); } }
When we compile the code, we are greeted with our old friend: error[E0382]: use of moved value: 'name'
.
This happens, because the name
variable and the ownership of the name
variable is passed to the greet()
function during the first execution of the loop. Subsequent iterations therefore no longer have access to the name
variable and can't run.
In this case we can easily borrow the name
variable to get us out of this situation:
fn greet(name: &str) { println!("Welcome {name}"); } fn main() { let name = "Marcel".to_string(); for _i in 0..10 { greet(&name); } }