Introduction

In our previous post we have seen how to compile our first Rust program. But we did not really do anything, everything was already written for us. In this post we will discover how to change the code and make the computer talk to us!

Understanding our code

The code that was compiled came from the main.rs file.

fn main() {
    println!("Hello, world!");
}

The Rust programming language is made of keywords having specific meaning for the computer. The first keyword we can see here is the fn keyword. This keyword means that the following will be a function. Hence, main is a function. You can easily recognize functions because they are always followed by a pair of round brackets. We will learn more about functions in a later post but what you should remember here is that the code that will be executed by your program is inside the main function. But what is “inside” ? Do you see the “{“ and “}” symbols? This curly bracket pair defines the beginning and end of the main function. Whatever you will write inside will belong to the main function.

Here, the only instruction inside the main function is

println!("Hello, world!");

println is called a macro. You can recognize macros by the “!” at the end. You do not need for now to care about the meaning of a macro, just think of it as a black box. You can already guess the purpose of this macro: it prints in the console the text you write inside. But again, what is “inside” for a macro? “Inside” is defined by whatever is between the pair of round brackets “(“ and “)”. We call argument the input we give to the macro. So, in this case we put inside the println macro the following argument:

"Hello, world!"

What about the semicolon “;”? Well, there are some subtelty but for now think of it as an indicator for the computer that the instruction is done. Most lines of Rust code will end with a semicolon.

A bit of modification

From now I will assume that you will change the code on your computer and compile/run it for each example I will give you. Remember that we can only learn how to program by practicing a lot, so do not hesitate by experiment by yourself! A small reminder, to compile your code you can type cargo build and to run it you can type cargo run. Besides, you do not really need to separate this two steps, in fact, cargo run will compile and run your program in one step!

We now understand what our program is doing. Good, now let’s modify it! A straightforward modification would be to modify the argument of the println macro:

fn main() {
    println!("I can talk!!!");
}

And it should work well. Your computer can write sentences! What about repeating the instruction?

fn main() {
    println!("I can talk!!!");
    println!("I can talk!!!");
}

The result should be something like this:

I can talk!!!
I can talk!!!

Nothing surprising. You asked the computer to print two times “I can talk!!!” and it did. Now, what if you forgot to write a semicolon?

fn main() {
    println!("I can talk!!!");
    println!("I can talk!!!")
}

Nothing changes, everything is good. But, what about this?

fn main() {
    println!("I can talk!!!")
    println!("I can talk!!!");
}

Suddenly we got this message:

error: expected one of `.`, `;`, `?`, `}`, or an operator, found `println`
 --> src/main.rs:3:5
   |
   2 |     println!("I can talk!!!")
     |                              - expected one of `.`, `;`, `?`, `}`, or an operator here
     3 |     println!("I can talk!!!");
       |     ^^^^^^^ unexpected token

error: aborting due to previous error

error: Could not compile `my_project`.

To learn more, run the command again with --verbose.

This is a message from the compiler! If you remember from the last post, the compiler is translating your Rust code to machine code. However, the Rust code should follow some rules. One of them is that each instruction should be separated by a semicolon. If you do not put a semicolon at the end of the second line (the first println instruction) the compiler will think that the instructions continues on the third line (the second println instruction) which is an error.

You should think of the compiler as a friend who will tell you if your code has some mistakes that you should correct before translating it to machine code. It will avoid you to compile your code into a program acting in a different way that you expected. One of Rust good selling point is the compiler error messages. Most of them are easy to understand and guide you on how to solve the problem. Here, the compiler says that he was expecting an operator (the semicolon “;”) but instead it found the println macro in the third line (remember that the compiler does not care about line break). So if you want to fix it, you should add a semicolon at the end of the instruction.

The compiler does not care about line break, but it also does not care about blank space! So if you want you could write

fn main() {
    println!("I can talk!!!")       ;
    println!("I can talk!!!");
}

The compiler will understand it the same! However, it is better to write it just at the end of the line as it is easier to read for most programmers. You will soon see that Rust will try to make you follow writing conventions. It may seems arbitrary at first and constraining but I promise you that at the end you will like to be able to read other people code easily because they respect the same convention as you do.

Another thing, you can write non-existing instructions! You just need to write a semicolon with nothing before.

fn main() {
    println!("First instruction");
    ;
    println!("Third instruction");
}

It is of course totally useless, but it is understood by the compiler.

Ok, so everything is clear right? Wait! Why did the compiler throw an error when the first semicolon was forgotten but not when it was the second one? Like I said before, there are some subtelty on how to indicate that an instruction is finished in Rust. For now you can think of it as that the last instruction of the main function does not need a semicolon to indicate that it is the end. However, you can still put one! From now on I will always write a semicolon even if it not necessary. We will see later in which case we can remove it.

Here is a last program. I advise you to try modify it by yourself!

fn main() {
    println!("Hi! I am your computer, nice to meet you!");
    println!("Do you know that I can talk in multiple languages?");
    println!("Here is some Japanese: 日本語を話せます!");
    println!("Or some French: Je peux aussi parler français.");
    println!("And I can count: 1+1=2 !");
}

Conclusion

In this post we have seen what our program was really doing and we modified it to make our computer talked to us. However, it is still us who told it everything it has to say. In the next post we will make the computer do some computations for us!