Introduction

In our previous post we discovered variables and number types. Today we will see a new type that will allow us to program conditions. A simple application will be to create a task reminder.

Booleans

Until now, our variables were representing numbers. Another interesting kind of variables are booleans. A boolean is a variable with only two possible values: true or false. This is useful when you want to make some conditions in your program like “If this condition is true then…”. Creating a boolean is easy, we can either initialize the variable with true or false and the compiler will infer the type by itself or we can use type annotation by using the bool type.

fn main() {
    let a = true;
    let b: bool = false;
}

A boolean weights one byte of memory, which is equivalent to 8 bits. Could we print a boolean with println?

fn main() {
    let a = true;
    let b: bool = false;
    println!("Eating an apple is healthy: {}", a);
    println!("The sky is red: {}", b);
}
Eating an apple is healthy: true
The sky is red: false

We can! Alright, how can we use this new kind of variable?

The if expression

One fundamental part of programming is to make the program behave differently if some conditions are met or not. Think about when we are logging in a website. When we write our password and then click to log there are two possibilities: either we are a legitimate user who registered before (the username and password are correct) or not. Depending on these possibilities, the website will either ask us again our credentials or will continue on another page for the registered users.

In Rust, we can do this by using the if keyword.

fn main() {
    let condition = true;
    if condition {
        println!("The condition is true");
    }
}

The condition (which is a boolean) is placed just after the if and the instructions to execute if the condition is met (condition is true) are placed inside the curly braces. We can create many conditions by using numbers. For example, we can test the equality condition by using the “==” operator.

fn main() {
    let number = 5;
    if number == 3 {
        println!("The number is equal to 3");
    }
}

The “number == 3” expression is in fact a boolean. You can test it by checking its type:

fn main() {
    let number = 5;
    let variable = number == 3;
    variable = ();
}
error[E0308]: mismatched types
 --> src/main.rs:4:16
  |
4 |     variable = ();
  |                ^^ expected bool, found ()
  |
  = note: expected type `bool`
              found type `()`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

We can also test if the number is different from another one with the “!=” operator.

fn main() {
    let number = 5;
    if number == 3 {
        println!("The number is equal to 3");
    }
    if number != 3 {
        println!("The number is not equal to 3");
    }
}

We used integers but floats can also be used. An other possible condition is the inequality condition: if a number is less or greater than another number. For this, we can use the “<” or “>” operators.

fn main() {
    let number = 113.48;
    if number > 42.0 {
        println!("This number is really big!");
    }
    if number < 42.0 {
        println!("This is a small number!");
    }
}

We should note that when doing comparisons between numbers we should be careful that both numbers have the same type. We cannot compare an integer with a float.

In our last example, we cover all the number less than 42 and greater than 42 but what about if our number was 42 exactly? We can have inequality conditions checking for equality with the operators “>=” and “<=”.

fn main() {
    let number = 42.0;
    if number > 42.0 {
        println!("This number is really big!");
    }
    if number <= 42.0 {
        println!("This is a small number!");
    }
}

However, what happens if we put both “<=” and “>=”?

fn main() {
    let number = 42.0;
    if number >= 42.0 {
        println!("This number is really big!");
    }
    if number <= 42.0 {
        println!("This is a small number!");
    }
}
This number is really big!
This is a small number!

We got both answers! Actually, this is logical. The first condition is tested and equal to true because the number is 42, therefore “This number is really big!” is printed. Then, the next instruction is the second if expression, which is also verified! In most case, this will not be what we want to do. Usually, we want to check if a condition is verified or else do something else. Rust allows us to do it by using the else keyword.

fn main() {
    let number = 42.0;
    if number >= 42.0 {
        println!("This number is really big!");
    }
    else {
        println!("This is a small number!");
    }
}

When you are using the else keyword you are creating an exclusive choice. It is either the instructions inside the if expression or the ones in the else expression but never both. But what if we want a little bit more choices? For example, in our code we just have the choice between being a big number or a small number. What about when it is exactly 42? Shouldn’t it be perfectly sized (it is the answer for the ultimate question of Life, the Universe and Everything after all!) ? In this situation, Rust allows multiple choices with the else if keyword.

fn main() {
    let number = 42.0;
    if number > 42.0 {
        println!("This number is really big!");
    }
    else if number == 42.0 {
        println!("This is a perfectly sized number!");
    }
    else {
        println!("This is a small number!");
    }
}

An else if expression will work exactly like an if expression except that if the if expression is entered (the condition has been met) then the following else if expression will not be evaluated. It is still an exclusive choice.

Here is a table summarizing the different operator we have seen.

Operator Meaning
== is equal to
!= is different than
< is strictly less than
<= is less or equal to
> is strictly greater than
>= is greater or equal to

A task reminder

Let us do a simple program to show how we could use what we just learned. Imagine that you are working on your computer and you want a reminder to tell you to be ready in to do a task. Every you want a message on your computer until the task should be done. However, depending on if the task is urgent or not you would like to have different messages. Of course, when it is urgent the messages have to be more ardent!

So, how could we code this? First we need to analyse our problem. We want a program that should execute some instructions depending on one condition, the urgency of the task. The task is either urgent or not, so the logic of the program will be represented by an if/else expression. Besides, to represent the urgency value we can use a boolean. Therefore a skeleton of program would be the following.

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent 
    if urgent {
        
    }
    else {
        
    }
}

Good! Now, what’s next? When the task is urgent, the program should print some text every until have been passed. It means that we need a way to measure time. It is possible but this is not something done by using the Rust language directly, instead we will use the computer operating system.

The operating system of the computer does many many thing, one of it is to track time. What we want to do is to tell our program to wait and then continue.

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent 
    if urgent {
        println!("You have an urgent task in 10s.");
	// wait for 2s
        println!("Be ready, it will soon happen!");
	// wait for 2s
        println!("Finish everything else, the task is more important!!");
	// wait for 2s
        println!("It is URGENT!!!");
	// wait for 2s
        println!("IT IS HAPPENING!!!!");
	// wait for 2s
        println!("DO IT!!!!!");
    }
    else {
        
    }
}

So how can we make the program wait? Many problems in programming are fairly commons like how to ask the operating system to make our program to wait. Instead of recreating by ourselves a solution (even if it is possible) we can just use the standard library. A library in programming is like a collection of functions and data structures that you can use for your own project. The standard library is the library made by the Rust developers.

What we want for our program is the sleep function. The description of the function is:

Puts the current thread to sleep for at least the specified amount of time.

But what is a function? Until now, we have only seen the main function where we are working everytime. We will see functions more extensively in a later post but for now you can think of it as a black box: you put an input and the function gives an output. In this case, the sleep function takes an input (named the argument) representing the time we want to wait, and the output of the function is to make our program “sleep” (doing nothing). If it is still unclear, fear not, we will explain in far more details later!

In the documentation of the sleep function you can see in the upper part

pub fn sleep(dur: Duration)

The pub keyword means that this is a public function, you have access to it and can use it. The fn keyword means that this is a function. Then we can seen inside the round brackets that there is an argument (a variable) named dur of type Duration. Until now, we have only see number types and boolean type. However, there are many many more! Here the Duration type will be used for variable representing…durations. We want to wait for so we should create a Duration of right?

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent
    let waiting_time: Duration = 2;
    if urgent {
        println!("You have an urgent task in 10s.");
	// wait for 2s
        println!("Be ready, it will soon happen!");
	// wait for 2s
        println!("Finish everything else, the task is more important!!");
	// wait for 2s
        println!("It is URGENT!!!");
	// wait for 2s
        println!("IT IS HAPPENING!!!!");
	// wait for 2s
        println!("DO IT!!!!!");
    }
    else {
        
    }
}
error[E0412]: cannot find type `Duration` in this scope
 --> src/main.rs:3:23
  |
3 |     let waiting_time: Duration = 2;
  |                       ^^^^^^^^ not found in this scope
help: possible candidates are found in other modules, you can import them into scope
  |
1 | use core::time::Duration;
  |
1 | use std::time::Duration;
  |

error: aborting due to previous error

For more information about this error, try `rustc --explain E0412`.

It does not work! The reason is clear: “cannot find type Duration in this scope”. The compiler does not know about the Duration type. The definition of this type is, in fact, inside the standard library so we should tell the compiler to look there. Note that the compiler already checked for us in the standard library! It gives us a hint by saying that two Duration types have been found in the standard library (more precisely one in the standard library and one in the core library, but it is not important for now). So which one should we use?

If you click on the Duration type in the sleep function documentation then you will arrive to this page. The head title is “Struct std::time::Duration” so we know we should use the std::time::Duration type. The compiler already explained how to do it.

use std::time::Duration;

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent
    let waiting_time: Duration = 2;
    if urgent {
        println!("You have an urgent task in 10s.");
	// wait for 2s
        println!("Be ready, it will soon happen!");
	// wait for 2s
        println!("Finish everything else, the task is more important!!");
	// wait for 2s
        println!("It is URGENT!!!");
	// wait for 2s
        println!("IT IS HAPPENING!!!!");
	// wait for 2s
        println!("DO IT!!!!!");
    }
    else {
        
    }
}

We used a new keyword use which means that we want to use something from a library, here the Duration type from the standard library. We will learn more about libraries and how to use them later but for now we need to remember that if we want to use types or functions define in a library we need to use the use keyword.

So, is it compiling?

error[E0308]: mismatched types
 --> src/main.rs:7:34
  |
7 |     let waiting_time: Duration = 2;
  |                                  ^ expected struct `std::time::Duration`, found integral variable
  |
  = note: expected type `std::time::Duration`
	              found type `{integer}`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

It is not, but this time the error changed. The compiler now knows what is the Duration type and is telling us that we tried to bound the value of type integer to the variable waiting_time of type Duration which is a mistake. How can we do it? In doubt, always look in the documentation!

Each Duration is composed of a whole number of seconds and a fractional part represented in nanoseconds.

With an example just after:

use std::time::Duration;

let five_seconds = Duration::new(5, 0);
let five_seconds_and_five_nanos = five_seconds + Duration::new(0, 5);

In documentations, there usually are no main functions but it is implied. Following the example, we create a duration of .

use std::time::Duration;

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent
    let waiting_time: Duration = Duration::new(2, 0);
    if urgent {
        println!("You have an urgent task in 10s.");
	// wait for 2s
        println!("Be ready, it will soon happen!");
	// wait for 2s
        println!("Finish everything else, the task is more important!!");
	// wait for 2s
        println!("It is URGENT!!!");
	// wait for 2s
        println!("IT IS HAPPENING!!!!");
	// wait for 2s
        println!("DO IT!!!!!");
    }
    else {
        
    }
}

This time it compiles! But what does Duration::new means? Again, we will see more extensively later the exact meaning of it but basically it is a call of the function new from the type Duration. The output of this function new is of type Duration which is exactly what we want.

Okay, we now have our waiting_time variable of type Duration, how can we use the sleep function? Well, here was the definition of the function:

pub fn sleep(dur: Duration)

So, we just need to input the duration into the sleep function.

use std::time::Duration;

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent
    let waiting_time: Duration = Duration::new(2, 0);
    if urgent {
        println!("You have an urgent task in 10s.");
	sleep(waiting_time);
        println!("Be ready, it will soon happen!");
	sleep(waiting_time);
        println!("Finish everything else, the task is more important!!");
	sleep(waiting_time);
        println!("It is URGENT!!!");
	sleep(waiting_time);
        println!("IT IS HAPPENING!!!!");
	sleep(waiting_time);
        println!("DO IT!!!!!");
    }
    else {
        
    }
}
error[E0425]: cannot find function `sleep` in this scope
 --> src/main.rs:8:2
  |
8 |     sleep(waiting_time);
  |     ^^^^^ not found in this scope
help: possible candidate is found in another module, you can import it into scope
  |
1 | use std::thread::sleep;
  |


...


error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0425`.

And…it does not compile. This time we have 5 errors, 1 each time the sleep function is called. The compiler is telling us that it does not know about this function but it knows that the function exists in the standard library. We need to tell the compiler about where is this function exactly by using the use keyword again.

use std::thread::sleep;
use std::time::Duration;

fn main() {
    let urgent: bool = true; // Boolean variable to know if the task is urgent
    let waiting_time: Duration = Duration::new(2, 0);
    if urgent {
        println!("You have an urgent task in 10s.");
	sleep(waiting_time);
        println!("Be ready, it will soon happen!");
	sleep(waiting_time);
        println!("Finish everything else, the task is more important!!");
	sleep(waiting_time);
        println!("It is URGENT!!!");
	sleep(waiting_time);
        println!("IT IS HAPPENING!!!!");
	sleep(waiting_time);
        println!("DO IT!!!!!");
    }
    else {
        
    }
}

And this times it works!! We should now complete the program by writing the instructions inside the else expression.

use std::thread::sleep;
use std::time::Duration;

fn main() {
    let urgent: bool = false; // Boolean variable to know if the task is urgent
    let waiting_time: Duration = Duration::new(2, 0);
    if urgent {
        println!("You have an urgent task in 10s.");
	sleep(waiting_time);
        println!("Be ready, it will soon happen!");
	sleep(waiting_time);
        println!("Finish everything else, the task is more important!!");
	sleep(waiting_time);
        println!("It is URGENT!!!");
	sleep(waiting_time);
        println!("IT IS HAPPENING!!!!");
	sleep(waiting_time);
        println!("DO IT!!!!!");
    }
    else {
        println!("You have a task in 10s.");
	sleep(waiting_time);
        println!("Do not worry, you still have time.");
	sleep(waiting_time);
        println!("It is slowly arriving, prepare yourself.");
	sleep(waiting_time);
        println!("The task is coming.");
	sleep(waiting_time);
        println!("Finish everything else, it is here.");
	sleep(waiting_time);
        println!("Do the task.");        
    }
}

This example was very instructive. When programming it is really important to decompose the task our program should perform into subtasks. In our case the program had to first verify if it was urgent or not and then, depending on the result, execute one set of instructions or another. Programming is like building a castle brick by brick. You do not need to put everything together directly. Instead, you can simply beginning by one wall, then another one, until everything is done. The important part is to have a clear vision of the whole architecture in your mind.

Another important thing we have seen is the use of the documentation. It is normal to not know everything by heart. Reading documentations or googling problems is a daily part of a programmer life, do not feel ashamed to not know something!

Exercises

1) Until now, we have seen multiple keywords. What happens when we choose a variable name to be the same as a keyword (like let, if, …)?

2) Write a program that prints for each digit its spelling. For example, if the digit is 1 then the program should print “one”. To do it, write a long list of else if expression.

3) Prime number test

  • What does being even for an integer means? Could you find a condition to test to verify it?
  • Try to write a program with a minimum number of if/else if/else expressions outputing if a number is a prime number or not. The input numbers will be positive integers less than 100.

4) Change the task reminder program to make it depend on a number symbolizing the urgency of the task. For example, non urgent could be 0, urgent 1 and extremely urgent 2.

Conclusion

This post was pretty long, but we made an interesting program! We learned about the if/else if/else expressions and how to use them with booleans. Also, we used them to code a task reminder warning the user that a task will soon arrive. To do it we discovered a little bit about the standard library. In the next post, we will once again talk about variables and learn how to make them change values. We will also discover how to make a program repeat some instructions in a loop.