Control flow
Recommended reading: Control flow
The Control flow chapter of the Rust book covers all the standard use cases in a very clear way. People with experience in any other programming language should have no issues with this concept. That's why I won't repeat it again in this chapter and assume you're familiar with the basics.
In this chapter, we'll focus on some of the hidden gems that may not be immediately recognized when reading the book.
Assigning values from an if
statement
fn main() { let age = 18; let is_child = if age < 18 { true } else { false }; println!("Is child: {is_child}"); }
Notice the lack of
;
behind the 'true' and 'false'. This allows us to return the value as a result of theif
statement.
Extending the concept of returning from an if
statement, we could write something like this. (Not arguing that there
are better ways of getting to the same result!)
fn main() { let age = 15; let is_teenager = if age < 18 { if age >= 10 { true } else { false } } else { false }; println!("Is teenager: {is_teenager}"); }
We can combine this with a function:
fn is_child_a_teenager(age: i32) -> bool { age >= 10 } fn main() { let age = 15; let is_teenager = if age < 18 { is_child_a_teenager(age) } else { false }; println!("Is teenager: {is_teenager}"); }
To effectively end up with:
fn is_child(age: i32) -> bool { age < 18 } fn is_child_a_teenager(age: i32) -> bool { age >= 10 } fn main() { let age = 15; let is_teenager = is_child(age) && is_child_a_teenager(age); println!("Is teenager: {is_teenager}"); }
Check these examples and pay attention to the (lack of) semicolons
;
where we are returning results.
Did you run
cargo clippy
on the first two examples? Did you see how Clippy can help you write better code?!
There is another type of control flow type in Rust that is commonly used; the match
operator.
The match
operator
Match operators can be used to match a single value against a variety of patterns. This is best demonstrated with an example:
fn main() { let age = 15; match age { 0..=9 => println!("child"), 10..=17 => println!("teenager"), _ => println!("adult"), } }
Match patterns must be exhaustive. If not all cases are explicitly handled, the last statement must be a "catch-all" without conditions.
As with if
statements, you can return a value from a match
statement.
fn main() { let age = 15; let description = match age { 0..=9 => "child", 10..=17 => "teenager", _ => "adult", }; println!("{description}"); }
Notice the
;
at the end of the match block!
Match blocks can also be used to replace a set of if...else
statements.
fn main() { let age = 15; let description = match age { _ if age < 10 => "child", _ if age >= 18 => "adult", _ => "teenager", }; println!("{description}"); }
The _
in the above examples are actually variables that we ignore. In Rust variables that are not used can be ignored
with an _
-prefix. Or just an _
, like in these examples. If needed, we can capture the value rather than ignore it:
fn main() { let age = 15; let description = match age { _ if age < 10 => "child".to_string(), _ if age >= 18 => "adult".to_string(), a => format!("teenager of {} years old", a), }; println!("{description}"); }
If you are - like me - a fan of early returns, you can use match
blocks to quickly stop execution. Typically this is
used to stop processing in case of a error, but for the sake of demonstration, have a look at this example:
fn age_group(age: i32) -> String { let valid_age = match age { _ if age < 0 => return "not born yet".to_string(), _ if age > 150 => return "seriously?!!".to_string(), validated => validated, }; match valid_age { _ if age < 10 => "child".to_string(), _ if age >= 18 => "adult".to_string(), a => format!("teenager of {} years old", a), } } fn main() { let age = 15; let description = age_group(age); println!("{description}"); }
Notice that
valid_age
only gets a value assigned when 0 <= age <= 150. The other conditions - with the explicitreturn
- exit the function early with an 'error' condition.
You can do some super powerful stuff with matching on patterns, like matching on parts of an array or struct, but we'll cover that a bit later.