Introduction

This is a freely available sample of the upcoming book, Begin Rust. We’re hard at work getting it ready for publication, but wanted to share some of the content early with people who may be interested. If you’d like to learn more or subscribe to a mailing list to get updates on the book’s progress, please check out the Begin Rust homepage.

If you have any feedback, please let us know on Twitter or on our Discourse instance.


Want to learn how to program? Want to learn Rust? Have no idea what those questions mean, but you’re still interested? Awesome! You’re in the right place.

Begin Rust is targeted at getting people of all experience levels up and running with programming with the Rust programming language. Don’t know what programming, a language, or Rust are? That’s fine, we’ll be addressing all of those questions. Age wise, we’re targeting this book to be applicable to young teenagers (13 years old) and up. It should be fine for adults too.

Instructions

This book is going to be interactive. If you want to learn Rust, you need to follow along with the exercises. Just reading this will not be enough.

To make it easy to get started, you can use the online Rust Playground for running the examples. There are other ways to do things, but we want to focus on the important stuff. You should probably open that link and bookmark it, you’ll be using it a lot. You’ll also probably want to put this book in a window next to the playground so you can read and code at the same time.

When I tell you to work on some code, you should avoid copy-pasting. Instead, type it in by hand. Is that slower? Yes. But it will train you to get comfortable with Rust. Especially for new programmers, you’ll want to get used to all the weird characters we use, like { and ;. That way, when we get to the more complicated stuff in this book, you won’t be thinking about typing, you’ll be thinking about the hard stuff.

Finally, you should explore. If you have an idea, and you’re curious if it will work: try it and see what happens! You can’t break anything. The worst that will happen is you’ll get a confusing message.

Exercise solutions

We don’t provide exercise solutions in the book. This is very much an intentional decision: providing solutions here makes it too easy to keep "cheating" and looking ahead, without trying to solve it yourself. But we also know that there will be times when you need some help.

We’ve created an online forum at https://chat.beginrust.com/ where you can discuss the book content with other learners, and the authors too. If you’re stuck, we recommend reaching out over there, or checking if someone has already asked the question.

Existing programmers

Initially, the concept of this book was to target new programmers explicitly. While writing, we made sure to explain concepts from the ground up, not assuming prior programming knowledge. We’ve also avoided making comparisons to other programming languages while writing. However, to our surprise, we found that a lot of existing programmers were interested in a book teaching Rust in this style, and so we’ve added this little section to help such programmers out.

There will be some parts of this book that will be pretty easy for an existing programmer. Boolean logic, for example, is probably something you don’t need much of a refresher on. Feel free to skim through those sections, we won’t hold it against you. You may still find it useful to do all of the exercises, though, just to get more comfortable with the syntax.

There are a few parts of Rust covered in this book that will be especially unusual compared to other programming languages. Here’s what we’ve found needs quite a bit of focus from experienced programmers:

  • The concept of blocks, semicolons, and everything-is-an-expression is pretty different in Rust. These concepts almost make Rust feel like a functional programming language…​ but not quite. Rust really sits in its own world here, and it’s worth preparing yourself for that mental shift.

  • Going along with this: you’ll end up returning values from functions without using the return keyword. A lot. For Rubyists and Haskellers, this may be natural. For C++ or Java developers, it will likely feel foreign.

  • Immutable-by-default can be a big curve ball. If you’re used to languages with all variables being mutable, you may find it surprising just how much you can get done without mutability.

There are probably others as well. To reiterate though: to keep the flow of the book easy for those without programming experience, we will not be calling out these points in the text of the book itself.

1. Hello World and variables

It’s customary when you learn a new programming language to start with what we call "Hello World." It’s a simple program that prints the message "Hello World". Not only is it fun to write your first program, but it makes sure you know how to run programs.

Remember what I said in the introduction: you can use the Rust Playground for doing this. Go to that page, and then delete whatever is there right now so you get a clean slate. Then type in the following code:

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

Then, in the top left of the screen, click on the "Run" button. If everything goes correctly, you’ll see a message at the bottom of your screen that says "Hello, world!" If so, congratulations, you’ve written your first program! If not, you should fix this before moving on.

Note
Even when you write a successful program, you’ll see something in your output that says "Standard error." This is normal, and can be ignored. It’s just the way that Rust Playground displays things.

Here’s a screenshot of what it looks like on my computer after successfully running the program:

01 hello world

Don’t be confused by the "standard error" at the bottom, it’s just the way the Rust playground outputs some extra information that we don’t need right now.

1.1. What did that mean?

There are a number of things to learn here. We’ll be going into a lot more depth on all of this throughout the tutorial. But to make it easier to talk about, we’ll start with some basic terms. If some of this seems confusing, don’t worry too much. We’ll be working with these things much more through the tutorial, and it will start to make even more sense.

fn

The fn is short for "function." In Rust (and most other programming language), a function means "tell me some information, and I’ll do some things and give you an answer."

main

The main function is a special function: it’s where your program starts.

()

These parentheses are the parameter list for this function. It’s empty right now, meaning there are no parameters. Don’t worry about this yet. We’ll see plenty of functions later that do have parameters.

{ }

These are called curly braces or brackets. Now that we’ve said "hey, we’re going to make the main function," we actually need to give it a body. The body lives inside these braces. The body will say what the main function actually does.

println!

This is a macro. It means "print and add a new line." Macros are very similar to functions. And for now, you can think of println as a function. The difference is that it ends with an exclamation point (!).

("Hello, world!")

This is the parameter list for the macro call. You see, we’re saying "call this macro called println with these parameters." Yes, this is just like how the main function has a parameter list. Except the println macro actually has a parameter. We’ll see more about it later.

"Hello, world!"

This is a string. Strings are a bunch of letters (or characters) put together. We put them inside the double quotes (") to mark them as strings. Then we can pass them around for macros like println!, and lots of other functions we’ll play with later.

;

This is a semicolon. It completes what’s called a statement. Statements do something specific. In this case, it’s calling the macro. There are other kinds of statements as well, which we’ll start to see soon.

Again, don’t worry too much about all these terms and what they mean. The best way to learn how to program is to actually program!

1.2. Interpolation

That’s a big word! But it’s not hard to show what interpolation means. Our first program printed out a simple message, "Hello, world!" But what if we want to build up a more complicated message? You can interpolate other values into the output. Let’s demonstrate:

fn main() {
    println!("My name is {}", "Michael");
}

We’re now passing two parameters to the println macro: the first string is "My name is {}". The println macro has special support for the {} braces. It means: hey, you see that next parameter? Stick it in here. This program is going to print "My name is Michael". And we separate the parameters by putting a comma.

Exercise

Modify this program so that it says your name and age. For example, "My name is Michael and I am 34 years old." Try both with and without the double quotes around the age and see what works. Hint: you’ll end up with 3 parameters to println!, separated by 2 commas.

1.3. Variables

Often, we don’t want to put all of the values we care about on one line like that. It can be more pleasant to define variables: convenient names that refer to the values we care about. We do that with let:

fn main() {
    let name = "Michael";
    println!("My name is {}", name);
}

Now, name is pointing at the string "Michael", and we can refer to that when we call println!. Variables are a core part of programming, and we’re not going to be able to explain all of their uses here. But get ready to see a lot of them!

So far, we’ve just played with strings. But computers are really good at math, and they can play with numbers too. Let’s see this in action.

Note
You haven’t forgotten that you’re supposed to be typing all of these code snippets into the Rust Playground and running them, have you?
fn main() {
    let apples = 10;
    println!("I have {} apples", apples);
}

Notice how I didn’t put the double quotes around the number 10? You can go ahead and do that, and the program will have the same output. But they mean different things. Right now, apples is a number. But with the double quotes, it’s a string. If all we’re going to do is output them, the difference isn’t that important. But let’s actually do some math.

fn main() {
    let apples = 10 + 5;
    println!("I have {} apples", apples);
}

As you’d probably guess, this is going to print out "I have 15 apples". However, if you instead wrapped it all with double quotes:

fn main() {
    let apples = "10 + 5";
    println!("I have {} apples", apples);
}

Now it says "I have 10 + 5 apples." And you can get even worse with code like this, which isn’t going to work at all:

fn main() {
    let apples = "10" + "5";
    println!("I have {} apples", apples);
}

Go ahead and put that in the Rust Playground and run it. You’ll get an error message that looks something like this:

error[E0369]: binary operation `+` cannot be applied to type `&str`
 --> src/main.rs:2:23
  |
2 |     let apples = "10" + "5";
  |                  ---- ^ --- &str
  |                  |    |
  |                  |    `+` cannot be used to concatenate two `&str` strings
  |                  &str

This looks big and scary. Eventually we’ll understand what this all means. For now, you need to know that when you press run, something called the compiler is checking to make sure your program makes sense. And if it doesn’t, it will give you an error message.

1.4. More math!

Let’s use Rust to do a bit of arithmetic. The basic operations are:

+

addition

-

subtraction

*

multiplication

/

division

Exercise

Try to guess what the answer will be before running this program:

fn main() {
    let answer = 6 / 2 + 4 * 3;
    println!("The answer is: {}", answer);
}

If you guessed 21: sorry, you’re out of luck. The answer is 15, because of the order of operations. Rust follows standard arithmetic rules, so it does 6 / 2 = 3, then 4 * 3 = 12, then 3 + 12 = 15.

But what if I want to force it to do 6 / 2 = 3, then 3 + 4 = 7, then 7 * 3 = 21? You need to use parentheses to do that.

Exercise

Add a left parenthesis ( and a right parenthesis ) to the program above so that the answer it prints is 21.

1.5. Multiple variables

We’re not limited to just one variable. It’s fine to define as many as we’d like:

fn main() {
    let x = 5;
    let y = 10;
    let name = "Michael";
    println!("My name is {}, and the answer is {}", name, x + y);
}

We can also use this technique to break up larger computations into smaller pieces:

fn main() {
    let x = 5 + 3;
    let y = 2 - 1;
    let z = x * y;
    println!("The answer is {}", z);
}
Exercise

Use multiple variables, and no parentheses, to compute (6 / 2 + 4) * 3.

You’re also allowed to reuse variable names. This mostly works the way you would expect. Try to guess the output of this program:

fn main() {
    let x = 5 + 3;
    let x = x * 2;
    let x = x - 6;
    let x = x / 2;
    println!("The answer is {}", x);
}

Each time we reuse a variable name, we are creating a brand new variable, not modifying the previous one. We call this shadowing: the new variable with the same name shadows the old variable, so you can’t access the old one anymore.

1.6. Types

We’ve spoken about numbers and strings. In Rust, every value has some type which tells you what kind of thing it is. When you have a string literal (something between double quotes) like "Hello, world!", its type is &str.

When you use let to declare a variable, you can give its type as well, like this:

fn main() {
    let name: &str = "Michael";
    println!("My name is {}", name);
}

Rust has something called type inference, which means that in many cases, you can skip putting in the type, and the compiler will figure it out for you. Sometimes though, we like to write the type out. It can make it easier to read code sometimes, and can help the compiler catch our mistakes better. But usually with simple things, we just skip it.

There’s a little bit more to numbers to talk about. The first thing is about integers versus floating point. Integers are whole numbers, like 5, -2, 0, etc. They are numbers that don’t have a decimal point or a fractional part. Floating point numbers can have decimal points. For now, we’re just going to talk about integers.

Within integers, there are signed and unsigned. Signed integers can have a negative sign, and can be positive or negative. Unsigned, as you might guess, cannot have a negative sign, and must be 0 or greater.

And finally, there’s the size of an integer, measured in terms of bits. A bit is either a 0 or 1, and makes up the basis of computers. If you have an 8-bit unsigned integer, you can have the numbers 0 through 255. An 8-bit signed integer, on the other hand, can have the numbers -128 to 127. If you look closely, it’s the same total count (256 possibilities), but one includes negatives, the other doesn’t.

In Rust, the way we talk about the types of these integers is i8 (for signed 8-bit integer), u32 (for unsigned 32-bit integer), etc. You can use 8, 16, 32, and 64. There’s also some support for 128, but you’ll almost never need it. There’s also a special isize and usize, which means "use whatever size the computer I’m on likes." These days, that’s usually 64.

Alright, that’s lots of funny business. Let’s get back to some actual code! For now, we’re going to pretend like all the numbers we work with are i32. Why that? It’s pretty commonly used. Eventually, we’ll deal with converting between different types of numbers and things like that. But we’ll keep it simple for now.

Exercise

Fix the program below by changing the type.

fn main() {
    let name: &str = "Michael";
    let age: &str = 34;
    println!("My name is {} and I am {} years old", name, age);
}

1.7. Summary

  • Every Rust program starts at the main function

  • Inside the main function, you can do things like call println

  • The println macro lets us do interpolation to produce output

  • We can define variables that hold onto values using let

  • We can perform basic arithmetic using +, -, *, and /

  • Each value has a type, which says what kinds of things it can hold

    1. A &str is the type of a string literal, like "Michael"

    2. There are lots of different number types, but we’ll mostly use i32

1.8. Exercises

  1. Write a program from scratch that prints "Hello World." Try to avoid looking at the examples in this lesson. This will help you nail down all the different symbols and words you need to write normal Rust code.

  2. Modify the program below by changing the FIXMEs into the correct types to make this compile.

    fn main() {
        let x: FIXME = -5;
        let y: FIXME = 7;
        let name: FIXME = "Michael";
        println!("My name is {}, and the answer is {}", name, x * y);
    }
  3. Write a program that prints the result of doing 5 + 3, then 6 - 2, then multiplying the two results together.

    1. Do it once using multiple variables.

    2. Do it once using a single variable.

    3. Do it once using no variables at all.

  4. Write a program that generates this output:

    Hello, world!
    I have 10 apples.
    Goodbye!

2. Anatomy of Rust

We’re going to learn about a lot of different things in Rust: functions, strings, and numbers are just some examples we’ve seen so far. As you program in Rust, you’re going to need to assemble these together into something that the language allows.

The purpose of this lesson is to give you an overall look at how Rust programs are correctly put together. We want to:

  • Cover the names of different things so we can refer to them

  • Give an idea of how all these things fit together

  • Start explaining rules of the right way to type them into the computer

2.1. Pairing and nesting

A lot of Rust involves lots of special symbols. Let’s look at our "Hello, world!" program again:

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

We have the left parenthesis (, right parenthesis ), left curly brace {, right curly brace }, semicolon ;, and double quotes ". Ignore the semicolon for now, we’ll discuss it a bit later. The others all come in pairs in our program:

  • Every left parenthesis has a matching right

  • Every left curly brace has a matching right

  • There’s no "left double quote" and "right double quote," but there are two double quotes which form a pair

This is pretty common in Rust. We use these special symbols to group stuff together. For example, the curly braces group together the body of the function. The second set of parentheses group the parameter list passed to the println! macro. And the double quotes group together the string parameter.

The other important part of all of this is that these pairs nest. In other words, you can put one pair of symbols inside another. And notice in the program above, we always close off the inner pair before closing the outer pair. In other words, the code above is correct, but this code is incorrect:

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

By moving the right curly brace before the right parenthesis, we’ve messed up the nesting, and our code is no longer valid. In order to write correct code, make sure that you close off each pair of symbols in the correct order.

Exercise

Fix this program by adding the closing symbols for each pair in the right order.

fn main() {
    {
        {
            println!("Hello nesting!

2.2. Layout

Look at this piece of code:

fn main() {
    println!("Hello, world!");
    {
        let name = "Michael";
        println!("Nice to meet you, {}!", name);
    }
    println!("Have a great day");
}

Now compare it to this one:

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


;{
 let name = "Michael"; println!(
"Nice to meet you, {}!", name

);
               } println!("Have a great day")
; }

These programs both do exactly the same thing, but the first is significantly easier to read, and frankly prettier. That’s because it’s following Rust’s recommended rules of layout and indentation. The computer doesn’t care about these rules at all. They’re entirely to make the code easier to work with for you, the dear programmer.

There are lots of different recommended rules for layout, like: within a pair of curly braces, indent each line by four extra spaces at the beginning of the line. This makes it easier for a programmer to see how things are grouped together in those pairs of symbols. Another common thing is putting spaces around operators. For example, instead of let x=2+3, we’ll normally write let x = 2 + 3.

Again, the computer doesn’t care: it just looks at the symbols to figure it out. But our fragile human brains tend to like things spaced out visually.

Fortunately, the computer isn’t indifferent to our suffering. In the Rust Playground, under the "tools" button at the top, there’s an option called "Rustfmt." This runs the rustfmt tool, which automatically applies Rust layout rules to your code to make it easier to read. The fmt is short for "format."

You should get in the habit of trying to follow layout rules, like indentation, and spacing between symbols, and so on. I won’t be spelling them out explicitly all the time, but I will be demonstrating best practice. But you can always double-check yourself by running Rustfmt on your code.

Exercise

Try to fix the bad version of the code above to look correct. First, do it manually, and then ask Rustfmt to clean up any problems that are still there.

2.3. Comments

Going along with “the computer doesn’t care” is something called comments. Comments are a way for you to add in some kind of message for other programmers who are reading it. Sometimes those other programmers are just you in six months, when you’ve forgotten everything about the code. So writing good comments can be helpful for both you and others.

There are two ways to write comments in Rust. The first is to use two forward slashes //. Then, everything up until the end of the line is ignored by the compiler. For example:

fn main() {
    // This line is entirely ignored
    println!("Hello, world!"); // This printed a message
    // All done, bye!
}

The other way is to use a pair of /* and */. The advantage of this kind of comment is that it allows you to put comments in the middle of a line of code, and makes it easy to write multi-line comments. The downside is that for lots of common cases, you have to type in more characters than just //. Let’s see it in action:

fn main(/* hey, I can do this! */) {
    /* This line is entirely ignored */
    println!("Hello, world!" /* sneaky comment */);
    /* All done, bye!
       I had a great time programming with you.
     */
}

Use whichever feels more comfortable in the situation.

Another thing we sometimes do is comment out code, where we turn a line of code we don’t like into a comment. For example, in this program:

fn main() {
    println!("Hello, world!");
    println!("I want to comment this out");
}

I can comment out the second println! with:

fn main() {
    println!("Hello, world!");
    //println!("I want to comment this out");
}

I’ll use comments throughout the code snippets to give you extra information and hints.

Exercise

Comment out the first and third println! calls below.

fn main() {
    println!("Hello, world!");
    println!("Still alive!");
    println!("I'm tired, good night!");
}

2.4. Expressions, values, and types

Let me give you a statement that will govern a lot of our time in Rust. Then let’s explore what it means:

An expression is evaluated to a value of a specific type

A lot of the work we do in programming is producing and using values. Values can be something like the number 9, true, the words "Hello world", and more. Your computer will end up storing these values somewhere in memory, and then you’ll use them in things like calling functions or macros.

Each and every value you produce will have a type. Types help the computer out a lot with things like knowing how much space to set up in memory to hold a value. But they also help out the programmer a lot by making sure we don’t do stupid things. For example, types help prevent us from trying to add the number 5 and the string "banana" together.

The way we produce values in Rust is with expressions. There are many different kinds of expressions, and we’ll be learning about them over the course of our lessons. An expression is a recipe you, the programmer, give to the computer for how to produce a value.

Let me give you some examples. The expression 3 + 4 says "computer, please add the numbers 3 and 4 together to make a new value." The expression apples * 2 says "get the value contained in the variable named apples and multiply it by the number 2." And here’s another interesting one: the expression 5 says "hey, give me the number 5." That’s called a literal, which means that it’s an expression that doesn’t need to be evaluated into a value. And finally, add(7, 8) is an expression that says "call the function add on the values 7 and 8."

More complex expressions can be built up by combining simpler expressions. For example, we can combine the ideas above with:

add(3 + 4, apples * 2)

Instead of using the literal number 7 as the first parameter to the add function, we can provide our own expression, 3 + 4. Instead of the number 8, we can say apples * 2. And assuming the variable apples has the number 4 in it, these two expressions mean exactly the same thing.

The process of turning an expression into a value is called evaluation. You can think of the expression above as a tree:

      add
     /   \
  (+)     (*)
 /   \    / \
3     4  2  apples

Evaluation starts at the bottom of this tree and works its way up. The expressions 3 and 4 are already values, so they don’t need to be evaluated further. Then we move up the tree to the + operator and apply it to those values. We end up with the new tree:

      add
     /   \
    7     (*)
          / \
         2  apples

Then we look at the expressions 2 and apples. 2 is already a value, but for apples, we need to evaluate it by looking up the value in the variable. Assuming it’s 4, we get the new tree:

      add
     /   \
    7     (*)
          / \
         2   4

Then we can apply the * operator to get:

      add
     /   \
    7     8

And finally we can call the add function on the parameters 7 and 8 to get 15, and our evaluation is complete.

Rust is a language that puts a high emphasis on expressions. Get ready to see a lot of them!

Exercise

Take a piece of paper and draw out an expression tree for the expression:

(3 + 4) * (1 + (5 - 2))

Then evaluate the tree yourself from the bottom up.

2.5. Variables, again

One common purpose for variables is to provide a convenient name to refer to values. For example, take this program:

fn main() {
    println!(
        "In the year {}, you'll turn {} if you're born in {}",
        2020,
        2020 - 1985,
        1985,
    );
}

We’ve used the numbers 2020 and 1985 twice each, and we have an expression 2020 - 1985 in the middle without a clear meaning. We can use variables to help us out:

fn main() {
    let current_year = 2020;
    let birth_year = 1985;
    let age = current_year - birth_year;
    println!(
        "In the year {}, you'll turn {} if you're born in {}",
        current_year,
        age,
        birth_year,
    );
}

The two programs do the same thing, but the second is arguably much clearer to understand. The reason this works is that each variable evaluates to the value stored inside it. Naming these values with variables helps us keep track of what the values mean, either years or age in this case.

Using variables is great practice in a lot of Rust code. For example, if you want to see how old someone will be in 2030, you now only have to change the year once, instead of needing to change it twice in the original version. Same thing with changing the birth year.

That said, throughout our upcoming lessons, a lot of the exercises will involve removing variables to make sure you stay comfortable with the idea of sticking expressions inside other expressions.

Exercise

Get rid of all of the variables in this program:

fn main() {
    let x = 5 + 2;
    let y = 7 * 3;
    let z = y - x;
    println!("{}", z);
}

2.6. Effects versus results

What is the value of this expression?

println!("Hello, world!")

Most people would think that it’s the string "Hello, world!" But that’s not actually the case. We need to talk about the difference between an effect and a result.

An effect is something that is caused when you evaluate an expression. In our example above, the macro call println! causes output to be printed to the screen. You could have a program that talks to Amazon with a function call order_pokeballs(12) which causes 12 Pokeballs to be sent to your house. That would be the effect of evaluating that expression.

On the other hand, the expression 2 + 3 has no effect. Instead, it has a result. With the expression 2 + 3, only your program itself knows when the expression has been evaluated. However, it produces the value 5 which can be used by the rest of the program. I can capture the result of 2 + 3 in a variable with:

let x = 2 + 3;

Or I can use it directly in another expression, like a function call:

some_function(2 + 3)

It’s tempting to think that the expression println!("Hello, world!") has no result at all. However, remember the rule I stated above:

An expression is evaluated to a value of a specific type

Every expression must evaluate to a value. So the println! macro call must produce a result. The thing is, it doesn’t have anything useful to produce.

Rust has a special type for that case. It’s called unit, and perhaps confusingly, it’s written in Rust as a pair of parentheses (). This is for both the type and the value unit. Let’s see what this looks like in code:

fn main() {
    let x: () = ();
    let y: () = println!("Hello, world!");
    assert_eq!(x, y);
    println!("All units are the same!");
}

() is a unit literal, and can be placed on the right hand side of the equal sign, which is how we define the x variable. We also give x an explicit type with : (). See: () works for both the type and the value.

Next, we define y as the result of the println macro call. This immediately evaluates the macro, produces the output, generates the unit result value, and stores it in y. The next line is a new macro, assert_eq!, which checks to make sure two values are the same. It’s really useful for testing that your code does what you think it does. Here’s how it works:

  • If the two values you pass to assert_eq are the same, nothing happens.

  • If they’re different, your program exits with something called a panic.

To sum up: every expression produces a value. But if it doesn’t have anything useful to produce, it produces unit ().

Exercise

Add in types for all of the variables in the next bit of code, and guess what the output of this program will be before running it. I put an underscore (_) in front of some variable names because they are not used, and I don’t want the compiler to warn you about them. Feel free to remove those underscores if you like.

fn main() {
    let name = "Michael";
    let x = println!("My name is {}", name);
    let this_year = 2019;
    let birth_year = 1985;
    let age = this_year - birth_year;
    let y = println!("I turned {} in {}", age, this_year);
    assert_eq!(x, y);
    let _z = println!("Thanks for chatting with me!");
}

2.7. Blocks and statements

Let’s revisit our Hello World program and build up from there:

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

The curly braces part is called a block. And as you’ve already seen in other examples above, you’re not limited to just one println! macro call. You can have as many as you like:

fn main() {
    println!("Hello, world!");
    println!("Still alive!");
    println!("I'm tired, good night!");
}
Exercise

Comment out all three of the println! calls and see what happens.

Let’s look at another program:

fn main() {
    let x: i32 = 4 + 5;
    println!("4 + 5 == {}", x);
}

Just for kicks, I’m going to wrap the right hand side of let x = in curly braces:

fn main() {
    let x: i32 = { 4 + 5 };
    println!("4 + 5 == {}", x);
}

What we’ve done here is created a new block, and put the expression 4 + 5 inside of it. Blocks themselves are expressions, and evaluating them evaluates the expression inside. So x still ends up holding the value 9.

We can even make our block more complicated:

fn main() {
    let x: i32 = {
        println!("I'm inside a block");
        4 + 5
    };
    println!("4 + 5 == {}", x);
}

Now, when we evaluate that block, we first evaluate the println! macro call, and then we evaluate 4 + 5. The semicolon is separating the two expressions inside our block. We can even put in more expressions:

fn main() {
    let x: i32 = {
        println!("I'm inside a block");
        println!("Still inside");
        4 + 5
    };
    println!("4 + 5 == {}", x);
}

And we can even put in expressions that don’t have any effects:

fn main() {
    let x: i32 = {
        println!("I'm inside a block");
        6 + 7; // where did the 13 go?!?
        println!("Still inside");
        4 + 5
    };
    println!("4 + 5 == {}", x);
}

What happens to the value produced by evaluating these expressions? The semicolon causes them to be thrown away. Let’s see this in practice with a broken program:

fn main() {
    let x: i32 = {
        println!("I'm inside a block");
        4 + 5;
    };
    println!("4 + 5 == {}", x);
}

This generates the error message:

error[E0308]: mismatched types
 --> main.rs:2:18
  |
2 |       let x: i32 = {
  |  __________________^
3 | |         println!("I'm inside a block");
4 | |         4 + 5;
  | |              - help: consider removing this semicolon
5 | |     };
  | |_____^ expected i32, found ()
  |
  = note: expected type `i32`
             found type `()`

Now, the resulting i32 value 9 that was produced by evaluating 4 + 5 is thrown away. Since there’s no other expression afterwards, the entire block ends up producing the dummy "nothing interesting" value unit (). But we told the computer that our x value will hold an i32 value, so it complains that the types don’t match up.

Fortunately, the compiler is able to help us out here, and figured out that we probably didn’t want that semicolon. And sure enough, removing it fixes the problem.

Each of those expressions ending with a semicolon is known in Rust as a statement. And a block is made up of 0 or more of these statements, followed by 0 or 1 expressions. When you evaluate a block, it will evaluate all of the statements in order. Then, if there is no expression at the end, it will produce a unit value. If there is an expression at the end, it will evaluate that expression and produce that value as the block’s result.

There are other kinds of statements than expressions with semicolons. One kind that you’ve seen is a let statement, which creates a new variable. We’ll see more kinds of statements as we continue with Rust.

Finally, you should pay attention to pairs and nesting within a statement. Each statement should have properly paired symbols like braces, parentheses, and double quotes, and these should nest correctly.

Exercise

What order do the output messages appear in this program? Can you explain why they occur in that order?

fn main() {
    let apples: i32 = {
        println!("I'm about to figure out how many apples there are");
        let x = 10 + 5;
        println!("Now I know how many apples there are");
        x
    };
    println!("I have {} apples", apples);
}

2.8. Summary

  • Rust uses a lot of special symbols like { and )

  • Many of these symbols come in pairs

  • These pairs of symbols need to nest inside each other

  • It’s good practice to follow layout rules to make your code easier to read

  • The rustfmt tool can help you get the layout right

  • You can use // or /* */ for adding comments to your code

  • An expression is evaluated to a value of a specific type

  • Most of our Rust code is writing expressions

  • You can draw out expressions as trees, and then evaluate from the bottom up

  • Variables can be a convenient way to capture expression results and give them useful names

  • Some expressions cause effects to happen

  • All expressions produce a result

  • But when an expression has nothing useful to produce, it produces (), aka unit

  • The body of a function is a block

  • Blocks can contain multiple statements, and end with an optional expression

  • The semicolon throws away the result of an expression

  • If you end your block with an expression without a semicolon, it will produce that value, otherwise it produces unit

2.9. Exercises

  1. Will the program compile? Why or why not? If not, try to fix it.

    fn main() {
        let name = {
            "Michael"
        };
        println!("My name is {}", name);
    }
  2. Add the closing pair to all of the unclosed symbols:

    fn main( {
        println!("4 + 5 == {}, {{ 4 + 5
  3. Write a Hello World program, but include a comment before the main function.

  4. Correct the indentation and general format of the following program. Use the rustfmt tool to check your work.

    fn main() {
    let pikachu
    = "yellow"; let charmander =
    "orange";
    let
    squirtle = "blue and green"
    ; println!
    ("Pikachu is {}, Charmander is {}, and Squirtle is {}.",
                pikachu,
                    charmander,
                        squirtle)
        ;}
  5. Look through the following program and answer these questions for yourself. Then run the program and see if you were right.

    • What do the expressions evaluate to?

    • Will there be effects? If so, what?

      fn main() {
          println!("Far over the Misty Mountains cold");
          "Dragon!!!";
          let dwarves: i32 = 13;
          let hobbit: i32 = {
              "Not a wizard";
              5 - 4
          };
          dwarves - 3;
          println!("The Company of Thorin has {} dwarves and {} hobbit, {} members in all.", dwarves, hobbit, dwarves + hobbit);
          println!("After the Battle of the Five Armies, there were only {} dwarves remaining.", dwarves - 3);
      
      }

3. Functions

So far, our programs have all had exactly one function in them, called main. As we said, main is the entry point to our program. But main is also a boring function: it doesn’t take any parameters, and it doesn’t produce any result value (at least, not the way we’ve seen it so far).

In this lesson, we’re going to work on defining and calling new functions.

3.1. Hello World, again

We’re going to rewrite our Hello World program, but this time it will use a basically useless helper function:

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

fn main() {
    say_hello();
}

We define say_hello just like we define main, no surprises there. Then, inside the body for main, we call the function by putting the name, followed by parentheses, followed by a semicolon. It’s very similar to how we call the println macro.

Exercise

Define another helper function say_goodbye, that says some kind of goodbye message. Then print hello and goodbye 4 times each.

The exercise begins to show you one advantage of a function. If you’re going to do things repeatedly, defining a helper function lets you skip rewriting the same thing over and over. (Though in this case, we have even better techniques we’ll learn about another time.)

3.2. Function parameters

Let’s bump up the power of our functions, and allow them to take parameters. A parameter (also known as an argument) is a value passed into a function from its caller. Parameters allow a function to do different things based on how they are called.

fn say_apples(apples: i32) {
    println!("I have {} apples", apples);
}

fn main() {
    say_apples(10);
}

We finally have something inside the parentheses! Our function say_apples takes a single parameter. We name that parameter apples, and it’s now a variable we can use inside the say_apples function. When we define a parameter to a function, we also need to give it a type. Here, we’ve said that the type of apples is a signed 32-bit integer, or an i32.

Exercise

Modify the program above so that it produces this output:

I have 10 apples
I ate an apple!
I have 9 apples
I ate an apple!
I have 8 apples

3.3. Function results

So far, we’ve been pretending like all functions produce no result. But this isn’t true. In fact, it’s the opposite: every function produces a result value. By default, the result type of a function is the trusty old unit () we became so familiar with in the last lesson.

How do we say what the result type of a function is? We use an "arrow":

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

Adding the -> () to our main function’s definition doesn’t change anything. The default result type is unit, and now we’ve explicitly said that it’s unit.

But returning unit values is boring! Let’s write a function that doubles any number you give it. This is going to take an i32 and give you back an i32. We can talk about the function’s signature as what it looks like from the outside:

fn double(x: i32) -> i32

Nice. The way we read this is "double is a function which takes one parameter, an i32, and gives you back an i32 as the result."

But how do we produce a result from the function. Watch closely, and see if you can see the trick:

fn double(x: i32) -> i32 {
    x * 2
}

fn main() -> () {
    println!("3 * 2 == {}", double(3));
}

Do you see a missing symbol in the body of double? That’s right, there’s no semicolon after x * 2. Let’s remember a few important things from the last lesson:

  • A function body is a block

  • A block may optionally end with an expression

  • If a block ends with an expression, then evaluating the block results in the value of that expression

And suddenly the world comes full circle. If you want your function to result in a specific value, you provide an expression at the end with the value you want.

And now let’s see what a nice friend the compiler is. Let’s say you got confused, and you accidentally added that semicolon after x * 2. You’ll get an error message including:

1 | fn double(x: i32) -> i32 {
  |    ------            ^^^ expected i32, found ()
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     x * 2;
  |          - help: consider removing this semicolon

The last line I quoted is the useful one: the compiler knows you probably don’t want the semicolon. It understands that you’re writing a function that has a non-unit value, so of course you can’t end your function with a statement!

Exercise

Modify the double function so that, before returning a result, it says "I’m going to double X" (filling in the value provided).

3.4. Other parameters

We’re not limited to just accepting a single numeric parameter. Functions can take multiple parameters, and they can take other types like strings. Just like when we call functions or macros, we separate multiple parameters with commas. And as a reminder, the type we have for string literals is &str. Let’s see an example of this in practice:

fn eat(count: i32, food: &str) {
    println!("You ate {} helpings of {}", count, food);
}

fn main() {
    eat(5, "apples");
    eat(8, "bananas");
}

The eat function has a parameter list that takes two parameters. The first is a variable called count of type i32. The second is a variable called food of type &str. We separate the two parameters in the parameter list with a comma. And when we call eat inside main, we separate the parameters we pass in with a comma.

Exercise

Fix the following program to make it produce the output below. You should not modify the main function at all. Instead, you’ll want to add a parameter list to greet, and then use those variables inside the function body.

fn greet() {
    println!("This isn't going to work yet");
}

fn main() {
    greet("Alice", 10);
    greet("Bob", 8);
    greet("Charlie", 6);
}
Hello Alice, you can have 10 apples.
Hello Bob, you can have 8 apples.
Hello Charlie, you can have 6 apples.

3.5. Variable and parameter scope

Let’s look at one of our examples from earlier:

fn say_apples(apples: i32) {
    println!("I have {} apples", apples);
}

fn main() {
    say_apples(10);
}

We have apples in the parameter list for say_apples. This means that we’re allowed to use the name apples inside the body of the say_apples function. However, we can’t use it inside the body of the main function. This program will not compile:

fn say_apples(apples: i32) {
    println!("I have {} apples", apples);
}

fn main() {
    say_apples(10);
    println!("apples == {}", apples);
}

Variable names are only visible inside a certain scope. When you have the function say_apples with apples in the parameter list, we end up with a variable named apples which is scoped to the body of the say_apples function. Each block in Rust creates its own scope for variable names. That block automatically gets everything from the parent scope. Let’s see how that works:

fn say_apples(apples: i32) {
    println!("I can use apples here: {}", apples);
    {
        let bananas = 20;
        println!("I can use apples here too: {}", apples);
        println!("And I can use bananas: {}", bananas);
    }
    println!("But I can't use bananas here {}", bananas);
}

fn main() {
    say_apples(10);

    println!("I can't use either here {} {}", apples, bananas);
}

Notice that inside the inner block in say_apples, we can use the apples variable from the parent block. We can also define a new bananas variable. But we can’t use that bananas variable outside of that block.

Exercise

Fix that program by commenting out the lines that don’t compile.

Here’s another thing about scope: you can shadow variable names. Shadowing hides an old variable with a new one with the same name. For example, try to guess the output of this program:

fn main() {
    let apples = 10;
    println!("apples == {}", apples);
    let apples = 20;
    println!("apples == {}", apples);
}

There’s one interesting impact of how blocks and shadowing work together. You can shadow a variable in one scope, but it may remain unshadowed in another scope. That’s abstract, so let’s see some code:

fn main() {
    let apples = 10; // apples is 10
    println!("apples == {}", apples);
    {
        let apples = 20; // shadow apples in this scope
        println!("apples == {}", apples);
    }
    // That block's scope is done
    // Now our original apples is back in scope
    // What do you think this will output?
    println!("apples == {}", apples);
}

This program will output:

apples == 10
apples == 20
apples == 10

This is because we:

  • Create a new variable apples with the value 10

  • Print out its value

  • Create a new scope inside a new block

  • Create a new variable apples which shadows the first apples, with the value 20

  • Print out the new value

  • Exit the block, exiting that scope and losing the new apples value

  • Now, back in the first scope, we can only see the original apples value, and print that

Exercise

What will this output?

fn main() {
    let apples = 10;
    println!("Before the block, I have {} apples", apples);
    {
        let apples = apples + 5;
        println!("Inside the block, I have {} apples", apples);
    }
    println!("After the block, I have {} apples", apples);
}

3.6. Anatomy of a function

Alright, time to review what we know about functions and how they’re put together. From what we’ve seen, functions consist of:

  1. The keyword fn to say "hey, get ready for a function!"

  2. The name of the function

  3. An opening parenthesis ( to begin the parameter list

  4. 0 or more parameters, separated by commas. Each parameter consists of:

    1. A variable name to capture the parameter

    2. A colon :

    3. A type name

  5. A closing parenthesis ) to end the parameter list

  6. An optional result (or return) type for the function, given by the arrow -> followed by a type.

    1. If you do not include a return type, the implied return type is unit ()

  7. A block for the function body

    1. Inside the block, you can use any of the variables named in the parameter list

    2. If you want to return a value from the function, you should end your block with an expression that has the return value

Let’s look at some examples of this:

fn subtract(x: i32, y: i32) -> i32 {
    x - y
}

The function name is subtract. It has a parameter list with two parameters. The first is named x and is an i32. The second is named y and is an i32. The function returns an i32. The body of the function uses both the x and y parameters. And if we wanted to call this function, we would use something like subtract(5, 2).

Exercise

What would be the result of calling subtract(5, 2)? Can you write a main function that prints the result of subtract to prove that you have the right answer?

Here’s another example:

fn greet(name: &str) {
    println!("Hello {}", name);
}

This time, the function name is greet, and it only takes one parameter. The parameter is called name and is a &str. There’s no explicit return type, so this function returns a unit value. The block performs a single statement, calling the println! macro to produce some output.

Exercise

Go back over other functions we’ve defined so far and make sure you can identify these various parts.

3.7. Calling functions

Let’s go into a bit more depth on how to call a function. Let’s see an example:

fn greet(name: &str) {
    println!("Hello {}", name);
}

fn main() {
    greet("Michael");
}

In order to call a function, we put the name of the function, followed by an open parenthesis, followed by the parameter list, followed by a close parenthesis. A function call itself is an expression, a fact we’ll exploit in a little bit.

The parameter list when calling a function is a comma-separated list of expressions. So instead of passing in a string literal above, we could do:

fn main() {
    let foo = "Michael";
    greet(foo);
}

The names we use in calling a function have nothing to do with the names for the parameters inside the function. In this example, we made a variable in main called foo. But when we call greet, greet will refer to it as name. We can also call functions multiple times with different parameters. And each time we do that, the variable in the function refers to a different value:

fn main() {
    greet("Michael");
    greet("Miriam");
}

We can also use more complicated expressions in parameter lists. For example, we can use math operations inside the parameter list itself:

fn subtract(x: i32, y: i32) -> i32 {
    x - y
}

fn main() {
    println!("{}", subtract(5 + 2, 1 + 3));
}

And, since function calls themselves are expressions, we can use them inside function calls. Let’s start with something a bit more verbose using a helper variable:

fn add(x: i32, y: i32) -> i32 {
    x + y
}

fn subtract(x: i32, y: i32) -> i32 {
    x - y
}

fn main() {
    let a = add(5, 2);
    let b = add(1, 3);
    println!("{}", subtract(a, b));
}

We can get rid of the variable a by replacing its usage in subtract parameter list with the call to add itself:

fn main() {
    let b = add(1, 3);
    println!("{}", subtract(add(5, 2), b));
}
Exercise

Get rid of the variable b above in the same way we got rid of add.

3.8. Is main special?

Is the main function special? Well, yes and no. On the "no" side: main is defined just like any other function. The name it has isn’t off limits in any way, it can be used like any other name in Rust.

fn main() {
    let main = "I can reuse the name main";
    println!("main == {}", main);
}

However, main is special in a different way: it’s the entry point to your program, or the first thing that’s run. It has to have a specific signature. For example, if you define your main function to take a parameter, things break, since the compiler doesn’t know where to get that value from:

fn main(_x: i32) {
}

Additionally, main is only allowed to return some types. It can return unit, which we’ve seen already. But if we try to return some other things, e.g.:

fn main() -> i32 {
    42
}

We get an error message:

error[E0277]: `main` has invalid return type `i32`
 --> src/main.rs:1:14
  |
1 | fn main() -> i32 {
  |              ^^^ `main` can only return types that implement `std::process::Termination`
  |
  = help: consider using `()`, or a `Result`

That message is a bit more complicated than we’re ready to deal with. It’s referring to a "trait" called Termination. And it’s referring to a type called Result. We’ll get to traits and Results later. For now, consider main a function which is only allowed to return unit.

3.9. Summary

  • The main we’ve seen so far is one example of a function.

  • We can define multiple other functions.

  • We can call functions similar to how we call macros (for example, println!).

  • Functions may take parameters, which are passed in from the caller.

  • Function parameters can be used as variables inside the body of a function.

  • Functions may return results of different types.

  • By default, functions return the unit () type.

  • A function body is a block, and may end with an expression, which will be its result value.

  • Function parameters are scoped to the function body.

  • Blocks create scopes for variables defined inside of them.

  • main is a normal function, but has some special rules:

    • It cannot take any parameters.

    • It can only have some special result types.

3.10. Exercises

  1. Will the program compile? Why or why not? If not, try to fix it.

    fn main() {
        42
    }
  2. Will the program compile? Why or why not? If not, try to fix it.

    fn main() {
        println!("{}", 42)
    }
  3. What is the output of this program?

    fn main() {
        let x = 5;
        {
            let x = 6;
            println!("First time: {}", x);
        }
        println!("Second time: {}", x);
    }
  4. Add the helper functions needed to make this program pass. Do not modify the main function itself.

    fn main() {
        let x = (5 + 3) * (6 + 4);
        let y = times(add_3(5), add_4(6));
        assert_eq!(x, y);
        println!("Good job!");
    }
  5. Rewrite the main function to not use any variables. You’ll still want to call both the double and triple function.

    fn double(x: i32) -> i32 {
        x * 2
    }
    
    fn triple(x: i32) -> i32 {
        x * 3
    }
    
    fn main() {
        let x = double(5);
        let y = triple(x);
        println!("Answer: {}", y);
    }
  6. Using only two println! calls, write a program that produces the following output:

    I'm counting to 10. Currently at: 1.
    I'm counting to 10. Currently at: 2.
    I'm counting to 10. Currently at: 3.
    I'm counting to 10. Currently at: 4.
    I'm counting to 10. Currently at: 5.
    I'm counting to 10. Currently at: 6.
    I'm counting to 10. Currently at: 7.
    I'm counting to 10. Currently at: 8.
    I'm counting to 10. Currently at: 9.
    Bored now!
  7. Define a quadruple function, which results in the input value times 4, built by calling the double function twice. To help you out, here’s a skeleton you should make work. assert_eq! is a way to make sure you’re producing the right output.

    fn double(x: i32) -> i32 {
        x * 2
    }
    
    fn quadruple() {
    }
    
    fn main() -> () {
        assert_eq!(4, quadruple(1));
        assert_eq!(8, quadruple(2));
        assert_eq!(12, quadruple(3));
        assert_eq!(48, quadruple(quadruple(3)));
        println!("Success!")
    }

    You’ll need to modify the parameter list, return type, and body of the quadruple function.

Appendix A: End of the sample

We hope you enjoyed the first three chapters of Begin Rust. If you’re interested in learning more or getting updates on book progress, please check out the Begin Rust homepage.

If you have any feedback, please let us know on Twitter or on our Discourse instance.