Control Flow
if
Expressions
Blocks of code associated with the conditions in if
expressions are sometimes called arms, just like the arm in match
expressions. Optionally, we can also include an else
expression (or else if
). It's also worth noting that the condition must be a bool
.
#![allow(unused)] fn main() { fn eight() -> i32 { if true { 8 } else { 4 } } println!("{}", eight()); // => 8 }
Using if
in a let
statement
Because if
is an expression, we can use it on the right side of a let
statement. Remember that blocks of code evaluate to the last expression in them. This means the values that have the potential to be results from each arm of the if
must be the same type.
#![allow(unused)] fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!(number is "{}", number); }
Repetitions
Rust has three kinds of loops: loop
, while
and for
.
Loops
#![allow(unused)] fn main() { loop { println!("again!"); } }
You might need to pass the result of a loop
. To do this, add the value you want returned after the break
expression, you use to stop the loop.
#![allow(unused)] fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; }
Conditional Loops with while
It's often useful for a program to evaluate a condition within a loop. While the condition is true, the loop runs.
#![allow(unused)] fn main() { let mut number = 3; while number != 0 { number = number - 1; } }
Looping Through a Collection with for
You can use a for
loop and execute some code for each item in a collection. The safety and conciseness of for
loops make them the most commonly used loop construct in Rust.
#![allow(unused)] fn main() { for number in (1..4).rev() { println("{}!", number); } }
.rev()
reverses the range
Loop label
If you have loops within loops, break
and continue
apply to the innermost loop at that point. You can optionally specify a loop label on a loop that we can then use with break
or continue
to specify that those keywords apply to the labeled loop instead of the innermost loop.
#![allow(unused)] fn main() { let mut count = 0; 'counting_up: loop { let mut remaining = 10; loop { if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } }
The match Control Flow Operator
Rust has an extremely powerful control flow operator called match
that allows you to compare a value against a series of patterns and then execute code based on which pattern matches. The power of match
comes from the expressiveness of the patterns and the fact that the compiler confirms that all possible cases are handled.
When the
match
expression executes, it compares the resulting value against the pattern of each arm, in order.
The code associated with each arm is an expression, and the resulting value of the expression in the matching arm is the value that gets returned for the entire match
expression.
Curly brackets typically are not used if the match arm code is short.
Another useful feature of match arms is that they can bind to the parts of the values that match the pattern. This is how we can extract values out of enum variants.
Combining match
and enums is useful in many situation. You will see this pattern a lot in Rust code: match against an enum, bind a variable to the data inside, and then execute code based on it.
#![allow(unused)] fn main() { struct Number { odd: bool, value: i32, } let one = Number { odd: true, value: 1 }; let two = Number { odd: false, value: 2 }; print_number(one); // => Odd number: 1 print_number(two); // => Even number: 2 // Same as with the if pattern fn print_number(n: Number) { match n { Number { odd: true, value } => println!("Odd number: {}", value), Number { odd: false, value } => println!("Even number: {}", value), } } }
One requirement for match
expressions is that they need to be exhaustive. At least one arm needs to match.
One way to ensure you have covered every possibility is to have a catchall pattern for the last arm. A particular pattern _
will match anything, but it never binds to a variable.
#![allow(unused)] fn main() { fn print_number(n: Number) { match n.value { 1 => println!("One"), 2 => println!("Two"), _ => (), } } }
The
()
is just the unit value, so nothing will happen in the_
case here.
Pattern syntax
Literals
#![allow(unused)] fn main() { 1 => println!("one") }
Named Variables
#![allow(unused)] fn main() { Some(x) => println!("x = {}", x) }
Multiple
#![allow(unused)] fn main() { 1 | 2 => println!("one or two") }
Range with the ..= Syntax
Ranges are only allowed with numeric values or char
values, because the compiler checks that the range isn’t empty at compile time.
#![allow(unused)] fn main() { 1..=5 => println!("one through five"), 'a'..='j' => println!("early ASCII letter"), }
Match guard
A match guard is an additional if
condition specified after the pattern in a match
arm that must also match, along with the pattern matching, for that arm to be chosen. Match guards are useful for expressing more complex ideas than a pattern alone allows.
fn main() { let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("The number {} is even", x), Some(x) => println!("The number {} is odd", x), None => (), } }
@ Bindings
The at
operator (@
) lets us create a variable that holds a value at the same time we are testing that value to see whether it matches a pattern.
#![allow(unused)] fn main() { match msg { Message::Hello { id: id_variable @ 3..=7, } => println!("Found an id in range: {}", id_variable), Message::Hello { id: 10..=12 } => { println!("Found an id in another range") } Message::Hello { id } => println!("Found some other id: {}", id), } }
Concise Control Flow with if let and while let
The if let
syntax lets you combine if
and let
into a less verbose way to handle values that match one pattern while ignoring the rest.
if let
can also introduce shadowed variables in the same way thatmatch
arms can:if let Ok(age) = age
.
#![allow(unused)] fn main() { let a = Some(40); let b = None; print_number(a); // => 40 print_number(b); // => No output fn print_number(n: Option<i32>) { if let Some(value) = n { println!("{}", value) } } }
Similar in construction to if let
, the while let
conditional loop allows a while loop to run for as long as a pattern continues to match.
#![allow(unused)] fn main() { while let Some(top) = stack.pop() { println!("{}", top); } }