In this chapter we will cover control flow, how to get your code to conditionally execute as well as be repeated when needed.
Introduction
In this chapter you will:
- Use if, else and else if to create different execution paths through your code.
- Apply loops to repeat statements.
- Add conditions to loops to either break or skip iterations.
- Iterate collections of data.
Create a new execution path with if
.
Let's consider these set of statements.
let salary = 220.2;
let other_income = 10.5;
let tax = 0.2; // percentage
let takehome_salary = (salary + other_income) * (1 as f32 - tax);
Above we have a calculation of someone's salary. Imagine though that someone is exempt from taxes if their income is below a certain income level, what then?
For this case, we can use an if
construct to express this condition.
Our code can now be expressed like so:
let no_tax_value = 300.0;
let salary = 220.2;
let other_income = 10.5;
let tax = 0.2; // percentage
let mut takehome_salary = salary + other_income;
if (salary + other_income) > no_tax_value {
takehome_salary = (salary + other_income) * (1 as f32 - tax); // tax deducted
}
The if
clause evaluates an expression (salary + other_income) > no_tax_value
that if evaluated to true
will run the code within the brackets. If expression evaluates to false
then the statement won't be run.
Your program has now two different execution paths depending on the sum of salary + other_income
.
Else, what happens if if
is false
You can extend your if
with an else
that runs if if
evaluates to false.
In this example a customer is trying to withdraw an amount from their account and it shows an error message if it can't go through with it:
let mut account_balance = 100.0;
let amount = 101.50;
if account_balance - amount > 0 as f32 {
account_balance -= amount;
} else {
println!("Can't withdraw {}", amount);
}
From above, the else
is placed right next to the if
. In fact, it can't exist without the if
.
Else if, if you have more than one condition
You've seen if
and else
so far, but there's one more construct else if
that we can use. The idea with else if
is to execute if the if
evaluates to false
. You can have any number of else if
. Here's how to use it:
let card_number = 11;
if card_number == 11 {
println!("Jack");
} else if card_number == 12 {
println!("Queen");
} else if card_number == 13 {
println!("King")
} else if card_number == 14 || card_number == 1 {
println!("Ace");
} else {
println!("Card number is {}", card_number);
}
As you can see above, we have a number of else if
constructs we can add after the initial if
.
if
as an expression
Many languages out there has what is called a ternary expression. This means that a value is assigned to a variable in the same row. Here's how it can look in other languages:
// javascript
let salary = 100;
let tax = 0.2;
let take_home_salary = salary > 200 ? salary * (1 as f32 - tax) : salary;
The value lies in not having to define an if
with brackets that takes up several rows potentially. However, Rust can do this, like so:
let salary = 100.0;
let tax = 0.2;
let take_home_salary = if salary > 200.0 { salary * (1.0 - tax) } else {salary};
println!("Salary is {}", take_home_salary);
Note how the code inside of if
and else
don't have semicolons ;
, which makes them expressions. So here you are using the expression based version of if
and else
.
Loops
Loops are used when you want to repeat a statement a number of times. That could be for example:
- command based program, where you ask a user for an input until they choose to quit the program
- iterating over something:
- a collection of something, for example "orders" and process each order
- iterating over files in a directory.
As you can see, there are many cases where looping a set of statements makes sense. Rust offers us many ways to loop.
loop
, looping statements
With this construct, you repeat statements forever or until the program is closed down. Here's example code:
loop {
println!("forever");
}
Interrupt a loop with break
You can interrupt the looping mechanism though by adding a break
to it. Consider the below code for how it would work:
let repeat_times: i32 = 5;
let mut index = 0;
loop {
if index == repeat_times {
break;
}
println!("Loop {}", index + 1);
index += 1;
}
This program will print the following:
Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
The preceding code would print "forever" until you quit the program.
Returning from a loop
It's possible to return a value from a loop as you exit it with a break
statement.
Here's how you would write that:
let mut points = 0;
let result = loop {
if points >= 10 {
break points;
}
points += 2;
}
Here, points
is incremented for each iteration. Once the break condition is true, the current value of points
is returned back break points
and the value is assigned to result
.
While
You've seen so far how you can use loop
and define conditions inside of it and call break
to exit the loop. With while
, you can define the break condition as part of defining the while
, like so:
let mut points = 0;
while points < 10 {
points += 2;
}
println!("Value of points is {}", points);
Here, your break condition is defined right after the while
keyword. There's no need to use an if
or break
keyword to quit the loop.
Which version is easiest to read,
loop
orwhile
?
Iterate a collection
A common reason for wanting to repeat a statement is processing something that has the character of a sequence. It could for example be:
- a list of points awarded in a swim competition
- a set of items in shopping basket
To define a list, we can use the following syntax:
let judges_result = [8, 7, 8, 9, 9];
To list a specific items in a list, we use the square brackets, []
and a number between 0 and the length of the list -1. In the above example, 0,1,2,3,4 are addressable indexes. Here's an example:
judges_result[2]; //8
Trying to access with index 5, would cause an error.
Iteration
So how do we iterate through iterates_result
? There are two approaches we could use:
-
while
, it's perfectly fine to use awhile
, however, we need to keep track on when to stop iterating as we can end up out of bounds. -
for in
. With this construct, we are guarenteed to only list items for as long as there are items in the list.
Lets try the first variant with while:
let judges_result = [8, 7, 8, 9, 9];
let mut index = 0;
while index < 5 {
println!("Judge: {0} is {1} ", index + 1, judges_result[index]);
}
Now, lets compare it to using for in
:
let judges_result = [8, 7, 8, 9, 9];
for vote in &judges_result {
println!("Judge: {} ", vote);
}
Wait, that's not a fair comparison, what happened to the index ?
To get the index, we need to use a slightly more complex construct:
for (i, vote) in judges_result.iter().enumerate() {
println!("Judge: {0} is {1} ", index + 1, vote);
}
What's happening here is that we call iter()
, followed by calling enumerate()
. Now we have access to a tuple that contains both the index, i
and our value, vote
.
REMINDER: a tuple is complex data type created by adding a parenthesis and comma separate values, like so:
fn create_tuple(a: i32, b: i32) -> (i32, i32) {
(a, b)
}
let (a,b) = create_tuple(2, 4);
println!("{0} {1}", a, b);
Assignment
Imagine someone read today's entries from a POS (Point of Sale system) from a CSV file into an array entries
. Now print out out the total and the average value of a purchase.
INFO: if there's a value in there below 0, it should be thrown a way, as it's an error when reading input.
Here's what running the program can look like:
Here's today's purchases:
33.5, 7.25, 15.5, 44.9
The total is: 101.15
The average purchase was: 25.2875
Solution
fn main () {
let entries = [33.5, 7.25, 15.5, 44.9, -1.0];
let mut total:f32 = 0.0;
let mut approved_entries = 0;
println!("Today's entries are");
for item in entries {
if item < 0 as f32 {
continue;
}
total += item;
print!("{},", item);
approved_entries += 1;
}
print!("\n");
println!("The total is {}", total);
println!("The average purchase was {}", total / approved_entries as f32);
}