Control flow features let you write code that takes one action in some situations and another action in different situations. They're how you express decisions in your code.
In this module, we're going to cover if, else if, and else; three different kinds of loops - loop, while, and for; and the match expression. We'll be defining and calling functions in this module,
so check out the previous module on functions
if you need to review.
Let's start with if, else if, and else. We can write code that only uses if by starting with the if keyword, then an expression that evaluates to a Boolean, then curly brackets containing the code that should run if the condition evaluates to true.
If the condition evaluates to false,
the code in the brackets won't get run.
Optionally, you can have an else block that goes with an if block that contains code to run,
if the condition evaluates to false.
You can also have multiple conditions by having an if block, then an else if
block with another condition,
as many else if blocks as you want,
and then an optional else block that is run
if all of the conditions evaluate to false.
Like with functions, we can get a value from an if or else block by leaving the semicolon off of the last expression in the block.
We can then put that value in a variable.
Note,
we do need a semicolon to end the variable assignment statement.
This code prints
Your discount is 50!.
Note that when we return a value from an if else expression for a variable,
there must be an else clause so that the variable always gets a value. And the types of the values must all be the same -
in this case,
i32.
Next,
let's talk about loops.
Loops let you run the same code repeatedly.
The loop keyword lets you specify a block of code that should be run forever.
This loop prints hello, world!
over and over again.
This program will run until the end of time.
We've stopped it using Ctrl + C.
You can use the break keyword to exit a loop.
This code will loop forever,
asking What's the secret word?
and waiting for input, unless you type in the correct secret word, rust. In that case, we'll break out of the loop.
When we run this program and type java, the loop asks us again,
and if we type rust,
the code breaks out of the loop and the program ends.
The code we've written here,
using loop, if, and break, could also be written using a while loop.
A while loop is written with the while keyword, then an expression that evaluates to true or false, then curly brackets and a block of code.
The code in the block runs over and over,
while the expression specified is true.
Here's the secret word example rewritten using a while loop.
It works exactly the same, but is a little bit more concise.
The condition for when the loop should exit is now part of the start of the loop,
rather than inside the loop somewhere.
The for loop is probably the most common loop in Rust code.
It lets you run some code for each item in a collection.
Unlike for loops in some other languages, like C,
you don't need to manage an index into the collection and worry about
off-by-one errors.
To write a for loop, use the for keyword,
then pick a variable name to use inside the loop body to refer to each individual item. Then, put the keyword in, and specify the collection you want to use.
Then, put curly brackets and the code that should be run for each item.
Here, for the collection, we're using the range syntax - two dots - to create a range of integers,
starting from 1 inclusive and ending before 11.
For each number in the range, the for loop will print Now serving number with the number of the current iteration through the loop.
We can see that this code starts by printing Now serving number 1, and ends with printing Now serving number 10.
Finally,
let's talk about match expressions.
match isn't something very many programming languages have.
It's sort of like a bunch of
if/else ifs and sort of like a switch
or case
statement,
but better for two reasons:
pattern matching and exhaustiveness checking.
First,
let's talk about pattern matching.
Pattern matching is a feature available in Rust in multiple places.
We actually used pattern matching when we destructured a tuple into parts
in the Data types module. Surprise!
Pattern matching is especially useful in match expressions.
We specify a list of patterns to test a value against, and the match expression tests the value against each pattern and stops if it finds a matching one.
Then, it runs the code that goes with that pattern.
Here's an example of a match expression.
We have an integer stored in the variable x,
and we want to match x against some patterns.
In this case,
the patterns are literal numbers.
What Rust does is it takes x,
which in this case is 3, and compares it to 1, which doesn't match.
So, it moves on to the next pattern,
2,
which also doesn't match, onto the next pattern,
3,
which does match.
So, Rust will print, Three's a crowd, and be done with this match expression.
The underscore in the last pattern is a catch-all that will match any value. So, the last arm functions like an else in a set of
if/else
if/else blocks.
There are lots of different ideas you can express with pattern matching.
This example shows some logic that might be used in a board game where you roll two dice.
We've hardcoded the values of the dice roll to simplify this example.
The first arm matches a role where both dice have the value 1. The second arm matches
any role where
one of the two dice is 5,
but it doesn't matter which one, and we use underscores in the tuple pattern to ignore any other value.
The vertical bar between the two patterns means or. Also, note that we can have multiple lines of code paired with the pattern by putting curly brackets around them.
Again, the last pattern is an underscore that will match any value for the whole tuple that hasn't already been matched.
This code prints,
You rolled at least one 5! Move and
then roll again!.
The other reason match expressions are awesome is that they must be exhaustive and cover every case.
This prevents bugs that can be caused by forgetting to handle a situation.
For example,
here's a match statement where we're checking two Booleans:
is_confirmed and is_active.
We've forgotten the case
where is_confirmed is true and is_active is false.
If we try to compile this, Rust
can see that we haven't covered every possibility. In this case,
it's even able to tell us which case we missed -
thanks,
Rust!
We would fix this either by adding the explicit case we missed or by using an underscore to explicitly acknowledge that we want to ignore all other cases.
In this module, we've covered the if/else
if/else construct that conditionally runs blocks of code;
the loop keyword that runs a block repeatedly
forever; the while loop that runs code
while a condition is true;
the for loop that iterates over each element in a collection; and the match expression that uses pattern matching and has exhaustiveness checking to handle all possibilities a value might be.
These are the building blocks of complex logic.
Next module,
let's explore enums - a way to define a custom type that works nicely with match expressions.