Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
14334 lines (10934 sloc) 479 KB

Updates

example workflow name

23 May 2021: Now available in Indonesian thanks to Ariandy/1kb.

2 April 2021: Added BuyMeACoffee link for those who would like to buy me a coffee.

1 February 2021: Now available on YouTube! Two months later: all done as of 1 April 2021 for 186 videos in total (slightly over 23 hours).

22 December 2020: mdBook can be found here.

28 November 2020: Now also available in simplified Chinese thanks to kumakichi!

27 November 2021: Videos for Easy Rust are now being recorded in Korean! 한국어판 비디오도 녹화 시작!

Introduction

Rust is a new language that already has good textbooks. But sometimes its textbooks are difficult because they are for native English speakers. Many companies and people now learn Rust, and they could learn faster with a book that has easy English. This textbook is for these companies and people to learn Rust with simple English.

Rust is a language that is quite new, but already very popular. It's popular because it gives you the speed and control of C or C++ but also the memory safety of other newer languages like Python. It does this with some new ideas that are sometimes different from other languages. That means that there are some new things to learn, and you can't just "figure it out as you go along". Rust is a language that you have to think about for a while to understand. But it still looks pretty familiar if you know another language and it is made to help you write good code.

Who am I?

I am a Canadian who lives in Korea, and I wrote Easy Rust while thinking of how to make it easy for companies here to start using it. I hope that other countries that don't use English as a first language can use it too.

Writing Rust in Easy English

Rust in Easy English was written from July to August 2020, and is over 400 pages long. You can contact me here or on LinkedIn or on Twitter if you have any questions. If you see anything wrong or have a pull request to make, go ahead. Over 20 people have already helped out by fixing typos and problems in the code, so you can too. I'm not the world's best Rust expert so I always like to hear new ideas or see where I can make the book better.

Part 1 - Rust in your browser

This book has two parts. In Part 1, you will learn as much Rust as you can just in your browser. You can actually learn almost everything you need to know without installing Rust, so Part 1 is very long. Then at the end is Part 2. It is much shorter, and is about Rust on your computer. That's where you will learn everything else you need to know that you can only do outside of a browser. Some examples are: working with files, taking user input, graphics, and personal settings. Hopefully, by the end of Part 1 you will like Rust enough that you will install it. And if you don't, no problem - Part 1 teaches you so much that you won't mind.

Rust Playground

See this chapter on YouTube

Maybe you don't want to install Rust yet, and that's okay. You can go to https://play.rust-lang.org/ and start writing Rust without leaving your browser. You can write your code there and click Run to see the results. You can run most of the samples in this book inside the Playground in your browser. Only near the end you will see samples that go beyond what you can do in the Playground (like opening files).

Here are some tips when using the Rust Playground:

  • Run your code with Run
  • Change Debug to Release if you want your code to be faster. Debug: compiles faster, runs slower, contains debug information. Release: compiles slower, runs much faster, removes debug information.
  • Click on Share to get a url link. You can use that to share your code if you want help. After you click share, you can click on Open a new thread in the Rust user forum to ask people there for help right away.
  • Tools: Rustfmt will format your code nicely.
  • Tools: Clippy will give you extra information about how to make your code better.
  • Config: here you can change your theme to dark mode so you can work at night, and many other configurations.

If you want to install Rust, go here https://www.rust-lang.org/tools/install and follow the instructions. Usually you will use rustup to install and update Rust.

🚧 and ⚠️

Sometimes the code examples in the book don't work. If an example doesn't work, it will have a 🚧 or a ⚠️ in it. 🚧 is like "under construction": it means that the code is not complete. Rust needs a fn main() (a main function) to run, but sometimes we just want to look at small pieces of code so it won't have a fn main(). Those examples are correct, but need a fn main() for you to run them. And some code examples show you a problem that we will fix. Those ones might have a fn main() but generate an error, and so they will have a ⚠️.

Comments

See this chapter on YouTube

Comments are made for programmers to read, not the computer. It's good to write comments to help other people understand your code. It's also good to help you understand your code later. (Many people write good code but then forget why they wrote it.) To write comments in Rust you usually use //:

fn main() {
    // Rust programs start with fn main()
    // You put the code inside a block. It starts with { and ends with }
    let some_number = 100; // We can write as much as we want here and the compiler won't look at it
}

When you do this, the compiler won't look at anything to the right of the //.

There is another kind of comment that you write with /* to start and */ to end. This one is useful to write in the middle of your code.

fn main() {
    let some_number/*: i16*/ = 100;
}

To the compiler, let some_number/*: i16*/ = 100; looks like let some_number = 100;.

The /* */ form is also useful for very long comments over more than one line. In this example you can see that you need to write // for every line. But if you type /*, it won't stop until you finish it with */.

fn main() {
    let some_number = 100; /* Let me tell you
    a little about this number.
    It's 100, which is my favourite number.
    It's called some_number but actually I think that... */

    let some_number = 100; // Let me tell you
    // a little about this number.
    // It's 100, which is my favourite number.
    // It's called some_number but actually I think that...
}

Types

Rust has many types that let you work with numbers, characters, and so on. Some are simple, others are more complicated, and you can even create your own.

Primitive types

See this chapter on YouTube

Rust has simple types that are called primitive types (primitive = very basic). We will start with integers and char (characters). Integers are whole numbers with no decimal point. There are two types of integers:

  • Signed integers,
  • Unsigned integers.

Signed means + (plus sign) and - (minus sign), so signed integers can be positive or negative (e.g. +8, -8). But unsigned integers can only be positive, because they do not have a sign.

The signed integers are: i8, i16, i32, i64, i128, and isize. The unsigned integers are: u8, u16, u32, u64, u128, and usize.

The number after the i or the u means the number of bits for the number, so numbers with more bits can be larger. 8 bits = one byte, so i8 is one byte, i64 is 8 bytes, and so on. Number types with larger sizes can hold larger numbers. For example, a u8 can hold up to 255, but a u16 can hold up to 65535. And a u128 can hold up to 340282366920938463463374607431768211455.

So what is isize and usize? This means the number of bits on your type of computer. (The number of bits on your computer is called the architecture of your computer.) So isize and usize on a 32-bit computer is like i32 and u32, and isize and usize on a 64-bit computer is like i64 and u64.

There are many reasons for the different types of integers. One reason is computer performance: a smaller number of bytes is faster to process. For example, the number -10 as an i8 is 11110110, but as an i128 it is 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110110. But here are some other uses:

Characters in Rust are called char. Every char has a number: the letter A is number 65, while the character ("friend" in Chinese) is number 21451. The list of numbers is called "Unicode". Unicode uses smaller numbers for characters that are used more, like A through Z, or digits 0 through 9, or space.

fn main() {
    let first_letter = 'A';
    let space = ' '; // A space inside ' ' is also a char
    let other_language_char = 'Ꮔ'; // Thanks to Unicode, other languages like Cherokee display just fine too
    let cat_face = '😺'; // Emojis are chars too
}

The characters that are used most have numbers less than 256, and they can fit into a u8. Remember, a u8 is 0 plus all the numbers up to 255, for 256 in total. This means that Rust can safely cast a u8 into a char, using as. ("Cast u8 as char" means "pretend u8 is a char")

Casting with as is useful because Rust is very strict. It always needs to know the type, and won't let you use two different types together even if they are both integers. For example, this will not work:

fn main() { // main() is where Rust programs start to run. Code goes inside {} (curly brackets)

    let my_number = 100; // We didn't write a type of integer,
                         // so Rust chooses i32. Rust always
                         // chooses i32 for integers if you don't
                         // tell it to use a different type

    println!("{}", my_number as char); // ⚠️
}

Here is the reason:

error[E0604]: only `u8` can be cast as `char`, not `i32`
 --> src\main.rs:3:20
  |
3 |     println!("{}", my_number as char);
  |                    ^^^^^^^^^^^^^^^^^

Fortunately we can easily fix this with as. We can't cast i32 as a char, but we can cast an i32 as a u8. And then we can do the same from u8 to char. So in one line we use as to make my_number a u8, and again to make it a char. Now it will compile:

fn main() {
    let my_number = 100;
    println!("{}", my_number as u8 as char);
}

It prints d because that is the char in place 100.

The easier way, however, is just to tell Rust that my_number is a u8. Here's how you do it:

fn main() {
    let my_number: u8 = 100; //  change my_number to my_number: u8
    println!("{}", my_number as char);
}

So those are two reasons for all the different number types in Rust. Here is another reason: usize is the size that Rust uses for indexing. (Indexing means "which item is first", "which item is second", etc.) usize is the best size for indexing because:

  • An index can't be negative, so it needs to be a number with a u
  • It should be big, because sometimes you need to index many things, but
  • It can't be a u64 because 32-bit computers can't use u64.

So Rust uses usize so that your computer can get the biggest number for indexing that it can read.

Let's learn some more about char. You saw that a char is always one character, and uses '' instead of "".

All chars use 4 bytes of memory, since 4 bytes are enough to hold any kind of character:

  • Basic letters and symbols usually need 1 out of 4 bytes: a b 1 2 + - = $ @
  • Other letters like German Umlauts or accents need 2 out of 4 bytes: ä ö ü ß è é à ñ
  • Korean, Japanese or Chinese characters need 3 or 4 bytes: 国 안 녕

When using characters as part of a string, the string is encoded to use the least amount of memory needed for each character.

We can use .len() to see this for ourselves:

fn main() {
    println!("Size of a char: {}", std::mem::size_of::<char>()); // 4 bytes
    println!("Size of string containing 'a': {}", "a".len()); // .len() gives the size of the string in bytes
    println!("Size of string containing 'ß': {}", "ß".len());
    println!("Size of string containing '国': {}", "国".len());
    println!("Size of string containing '𓅱': {}", "𓅱".len());
}

This prints:

Size of a char: 4
Size of string containing 'a': 1
Size of string containing 'ß': 2
Size of string containing '国': 3
Size of string containing '𓅱': 4

You can see that a is one byte, the German ß is two, the Japanese is three, and the ancient Egyptian 𓅱 is 4 bytes.

fn main() {
    let slice = "Hello!";
    println!("Slice is {} bytes.", slice.len());
    let slice2 = "안녕!"; // Korean for "hi"
    println!("Slice2 is {} bytes.", slice2.len());
}

This prints:

Slice is 6 bytes.
Slice2 is 7 bytes.

slice is 6 characters in length and 6 bytes, but slice2 is 3 characters in length and 7 bytes.

If .len() gives the size in bytes, what about the size in characters? We will learn about these methods later, but you can just remember that .chars().count() will do it. .chars().count() turns what you wrote into characters and then counts how many there are.

fn main() {
    let slice = "Hello!";
    println!("Slice is {} bytes and also {} characters.", slice.len(), slice.chars().count());
    let slice2 = "안녕!";
    println!("Slice2 is {} bytes but only {} characters.", slice2.len(), slice2.chars().count());
}

This prints:

Slice is 6 bytes and also 6 characters.
Slice2 is 7 bytes but only 3 characters.

Type inference

See this chapter on YouTube

Type inference means that if you don't tell the compiler the type, but it can decide by itself, it will decide. The compiler always needs to know the type of the variables, but you don’t always need to tell it. Actually, usually you don't need to tell it. For example, for let my_number = 8, my_number will be an i32. That is because the compiler chooses i32 for integers if you don't tell it. But if you say let my_number: u8 = 8, it will make my_number a u8, because you told it u8.

So usually the compiler can guess. But sometimes you need to tell it, for two reasons:

  1. You are doing something very complex and the compiler doesn't know the type you want.
  2. You want a different type (for example, you want an i128, not an i32).

To specify a type, add a colon after the variable name.

fn main() {
    let small_number: u8 = 10;
}

For numbers, you can say the type after the number. You don't need a space - just type it right after the number.

fn main() {
    let small_number = 10u8; // 10u8 = 10 of type u8
}

You can also add _ if you want to make the number easy to read.

fn main() {
    let small_number = 10_u8; // This is easier to read
    let big_number = 100_000_000_i32; // 100 million is easy to read with _
}

The _ does not change the number. It is only to make it easy for you to read. And it doesn't matter how many _ you use:

fn main() {
    let number = 0________u8;
    let number2 = 1___6______2____4______i32;
    println!("{}, {}", number, number2);
}

This prints 0, 1624.

Floats

Floats are numbers with decimal points. 5.5 is a float, and 6 is an integer. 5.0 is also a float, and even 5. is a float.

fn main() {
    let my_float = 5.; // Rust sees . and knows that it is a float
}

But the types are not called float, they are called f32 and f64. It is the same as integers: the number after f shows the number of bits. If you don't write the type, Rust will choose f64.

Of course, only floats of the same type can be used together. So you can't add an f32 to an f64.

fn main() {
    let my_float: f64 = 5.0; // This is an f64
    let my_other_float: f32 = 8.5; // This is an f32

    let third_float = my_float + my_other_float; // ⚠️
}

When you try to run this, Rust will say:

error[E0308]: mismatched types
 --> src\main.rs:5:34
  |
5 |     let third_float = my_float + my_other_float;
  |                                  ^^^^^^^^^^^^^^ expected `f64`, found `f32`

The compiler writes "expected (type), found (type)" when you use the wrong type. It reads your code like this:

fn main() {
    let my_float: f64 = 5.0; // The compiler sees an f64
    let my_other_float: f32 = 8.5; // The compiler sees an f32. It is a different type.
    let third_float = my_float + // You want to add my_float to something, so it must be an f64 plus another f64. Now it expects an f64...
    let third_float = my_float + my_other_float;  // ⚠️ but it found an f32. It can't add them.
}

So when you see "expected (type), found (type)", you must find why the compiler expected a different type.

Of course, with simple numbers it is easy to fix. You can cast the f32 to an f64 with as:

fn main() {
    let my_float: f64 = 5.0;
    let my_other_float: f32 = 8.5;

    let third_float = my_float + my_other_float as f64; // my_other_float as f64 = use my_other_float like an f64
}

Or even more simply, remove the type declarations. ("to declare a type" = "to tell Rust to use the type") Rust will choose types that can add together.

fn main() {
    let my_float = 5.0; // Rust will choose f64
    let my_other_float = 8.5; // Here again it will choose f64

    let third_float = my_float + my_other_float;
}

The Rust compiler is smart and will not choose f64 if you need f32:

fn main() {
    let my_float: f32 = 5.0;
    let my_other_float = 8.5; // Usually Rust would choose f64,

    let third_float = my_float + my_other_float; // but now it knows that you need to add it to an f32. So it chooses f32 for my_other_float too
}

Printing 'hello, world!'

See this chapter on YouTube: Video 1, Video 2

When you start a new Rust program, it always has this code:

fn main() {
    println!("Hello, world!");
}
  • fn means function,
  • main is the function that starts the program,
  • () means that we didn't give the function any variables to start.

{} is called a code block. This is the space where code lives.

println! is a macro that prints to the console. A macro is like a function that writes code for you. Macros have a ! after them. We will learn about making macros later. For now, remember that ! means that it is a macro.

To learn about the ;, we will create another function. First, in main we will print a number 8:

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

The {} in println! means "put the variable inside here". This prints Hello, world number 8!.

We can put more in, just like we did before:

fn main() {
    println!("Hello, worlds number {} and {}!", 8, 9);
}

This prints Hello, worlds number 8 and 9!.

Now let's create the function.

fn number() -> i32 {
    8
}

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

This also prints Hello, world number 8!. When Rust looks at number() it sees a function. This function:

  • Does not take anything (because it has ())
  • Returns an i32. The -> (called a "skinny arrow") shows what the function returns.

Inside the function is just 8. Because there is no ;, this is the value it returns. If it had a ;, it would not return anything (it would return a ()). Rust will not compile this if it has a ;, because the return is i32 and ; returns (), not i32:

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

fn number() -> i32 {
    8;  // ⚠️
}
5 | fn number() -> i32 {
  |    ------      ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
6 |     8;
  |      - help: consider removing this semicolon

This means "you told me that number() returns an i32, but you added a ; so it doesn't return anything". So the compiler suggests removing the semicolon.

You can also write return 8; but in Rust it is normal to just remove the ; to return.

When you want to give variables to a function, put them inside the (). You have to give them a name and write the type.

fn multiply(number_one: i32, number_two: i32) { // Two i32s will enter the function. We will call them number_one and number_two.
    let result = number_one * number_two;
    println!("{} times {} is {}", number_one, number_two, result);
}

fn main() {
    multiply(8, 9); // We can give the numbers directly
    let some_number = 10; // Or we can declare two variables
    let some_other_number = 2;
    multiply(some_number, some_other_number); // and put them in the function
}

We can also return an i32. Just take out the semicolon at the end:

fn multiply(number_one: i32, number_two: i32) -> i32 {
    let result = number_one * number_two;
    println!("{} times {} is {}", number_one, number_two, result);
    result // this is the i32 that we return
}

fn main() {
    let multiply_result = multiply(8, 9); // We used multiply() to print and to give the result to multiply_result
}

Declaring variables and code blocks

Use let to declare a variable (declare a variable = tell Rust to make a variable).

fn main() {
    let my_number = 8;
    println!("Hello, number {}", my_number);
}

Variables start and end inside a code block {}. In this example, my_number ends before we call println!, because it is inside its own code block.

fn main() {
    {
        let my_number = 8; // my_number starts here
                           // my_number ends here!
    }

    println!("Hello, number {}", my_number); // ⚠️ there is no my_number and
                                             // println!() can't find it
}

You can use a code block to return a value:

fn main() {
    let my_number = {
    let second_number = 8;
        second_number + 9 // No semicolon, so the code block returns 8 + 9.
                          // It works just like a function
    };

    println!("My number is: {}", my_number);
}

If you add a semicolon inside the block, it will return () (nothing):

fn main() {
    let my_number = {
    let second_number = 8; // declare second_number,
        second_number + 9; // add 9 to second_number
                           // but we didn't return it!
                           // second_number dies now
    };

    println!("My number is: {:?}", my_number); // my_number is ()
}

So why did we write {:?} and not {}? We will talk about that now.

Display and debug

See this chapter on YouTube

Simple variables in Rust can be printed with {} inside println!. But some variables can't, and you need to debug print. Debug print is printing for the programmer, because it usually shows more information. Debug sometimes doesn't look pretty, because it has extra information to help you.

How do you know if you need {:?} and not {}? The compiler will tell you. For example:

fn main() {
    let doesnt_print = ();
    println!("This will not print: {}", doesnt_print); // ⚠️
}

When we run this, the compiler says:

error[E0277]: `()` doesn't implement `std::fmt::Display`
 --> src\main.rs:3:41
  |
3 |     println!("This will not print: {}", doesnt_print);
  |                                         ^^^^^^^^^^^^ `()` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for `()`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
  = note: required by `std::fmt::Display::fmt`
  = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

This is a lot of information. But the important part is: you may be able to use {:?} (or {:#?} for pretty-print) instead. This means that you can try {:?}, and also {:#?} {:#?} is called "pretty printing". It is like {:?} but prints with different formatting over more lines.

So Display means printing with {}, and Debug means printing with {:?}.

One more thing: you can also use print! without ln if you don't want a new line.

fn main() {
    print!("This will not print a new line");
    println!(" so this will be on the same line");
}

This prints This will not print a new line so this will be on the same line.

Smallest and largest numbers

If you want to see the smallest and biggest numbers, you can use MIN and MAX after the name of the type:

fn main() {
    println!("The smallest i8 is {} and the biggest i8 is {}.", i8::MIN, i8::MAX); // hint: printing std::i8::MIN means "print MIN inside of the i8 section in the standard library"
    println!("The smallest u8 is {} and the biggest u8 is {}.", u8::MIN, u8::MAX);
    println!("The smallest i16 is {} and the biggest i16 is {}.", i16::MIN, i16::MAX);
    println!("The smallest u16 is {} and the biggest u16 is {}.", u16::MIN, u16::MAX);
    println!("The smallest i32 is {} and the biggest i32 is {}.", i32::MIN, i32::MAX);
    println!("The smallest u32 is {} and the biggest u32 is {}.", u32::MIN, u32::MAX);
    println!("The smallest i64 is {} and the biggest i64 is {}.", i64::MIN, i64::MAX);
    println!("The smallest u64 is {} and the biggest u64 is {}.", u64::MIN, u64::MAX);
    println!("The smallest i128 is {} and the biggest i128 is {}.", i128::MIN, i128::MAX);
    println!("The smallest u128 is {} and the biggest u128 is {}.", u128::MIN, u128::MAX);

}

This will print:

The smallest i8 is -128 and the biggest i8 is 127.
The smallest u8 is 0 and the biggest u8 is 255.
The smallest i16 is -32768 and the biggest i16 is 32767.
The smallest u16 is 0 and the biggest u16 is 65535.
The smallest i32 is -2147483648 and the biggest i32 is 2147483647.
The smallest u32 is 0 and the biggest u32 is 4294967295.
The smallest i64 is -9223372036854775808 and the biggest i64 is 9223372036854775807.
The smallest u64 is 0 and the biggest u64 is 18446744073709551615.
The smallest i128 is -170141183460469231731687303715884105728 and the biggest i128 is 170141183460469231731687303715884105727.
The smallest u128 is 0 and the biggest u128 is 340282366920938463463374607431768211455.

Mutability (changing)

See this chapter on YouTube

When you declare a variable with let, it is immutable (cannot be changed).

This will not work:

fn main() {
    let my_number = 8;
    my_number = 10; // ⚠️
}

The compiler says: error[E0384]: cannot assign twice to immutable variable my_number. This is because variables are immutable if you only write let.

But sometimes you want to change your variable. To make a variable that you can change, add mut after let:

fn main() {
    let mut my_number = 8;
    my_number = 10;
}

Now there is no problem.

However, you cannot change the type: even mut doesn't let you do that. This will not work:

fn main() {
    let mut my_variable = 8; // it is now an i32. That can't be changed
    my_variable = "Hello, world!"; // ⚠️
}

You will see the same "expected" message from the compiler: expected integer, found &str. &str is a string type that we will learn soon.

Shadowing

See this chapter on YouTube

Shadowing means using let to declare a new variable with the same name as another variable. It looks like mutability, but it is completely different. Shadowing looks like this:

fn main() {
    let my_number = 8; // This is an i32
    println!("{}", my_number); // prints 8
    let my_number = 9.2; // This is an f64 with the same name. But it's not the first my_number - it is completely different!
    println!("{}", my_number) // Prints 9.2
}

Here we say that we "shadowed" my_number with a new "let binding".

So is the first my_number destroyed? No, but when we call my_number we now get my_number the f64. And because they are in the same scope block (the same {}), we can't see the first my_number anymore.

But if they are in different blocks, we can see both. For example:

fn main() {
    let my_number = 8; // This is an i32
    println!("{}", my_number); // prints 8
    {
        let my_number = 9.2; // This is an f64. It is not my_number - it is completely different!
        println!("{}", my_number) // Prints 9.2
                                  // But the shadowed my_number only lives until here.
                                  // The first my_number is still alive!
    }
    println!("{}", my_number); // prints 8
}

So when you shadow a variable, you don't destroy it. You block it.

So what is the advantage of shadowing? Shadowing is good when you need to change a variable a lot. Imagine that you want to do a lot of simple math with a variable:

fn times_two(number: i32) -> i32 {
    number * 2
}

fn main() {
    let final_number = {
        let y = 10;
        let x = 9; // x starts at 9
        let x = times_two(x); // shadow with new x: 18
        let x = x + y; // shadow with new x: 28
        x // return x: final_number is now the value of x
    };
    println!("The number is now: {}", final_number)
}

Without shadowing you would have to think of different names, even though you don't care about x:

fn times_two(number: i32) -> i32 {
    number * 2
}

fn main() {
    // Pretending we are using Rust without shadowing
    let final_number = {
        let y = 10;
        let x = 9; // x starts at 9
        let x_twice = times_two(x); // second name for x
        let x_twice_and_y = x_twice + y; // third name for x!
        x_twice_and_y // too bad we didn't have shadowing - we could have just used x
    };
    println!("The number is now: {}", final_number)
}

In general, you see shadowing in Rust in this case. It happens where you want to quickly take variable, do something to it, and do something else again. And you usually use it for quick variables that you don't care too much about.

The stack, the heap, and pointers

The stack, the heap, and pointers are very important in Rust.

The stack and the heap are two places to keep memory in computers. The important differences are:

  • The stack is very fast, but the heap is not so fast. It's not super slow either, but the stack is always faster. But you can't just use the stack all the time, because:
  • Rust needs to know the size of a variable at compile time. So simple variables like i32 go on the stack, because we know their exact size. You always know that an i32 is going to be 4 bytes, because 32 bits = 4 bytes. So i32 can always go on the stack.
  • But some types don't know the size at compile time. But the stack needs to know the exact size. So what do you do? First you put the data in the heap, because the heap can have any size of data. And then to find it a pointer goes on the stack. This is fine because we always know the size of a pointer. So then the computer first goes to the stack, reads the pointer, and follows it to the heap where the data is.

Pointers sound complicated, but they are easy. Pointers are like a table of contents in a book. Imagine this book:

MY BOOK

TABLE OF CONTENTS

Chapter                        Page
Chapter 1: My life              1
Chapter 2: My cat               15
Chapter 3: My job               23
Chapter 4: My family            30
Chapter 5: Future plans         43

So this is like five pointers. You can read them and find the information they are talking about. Where is the chapter "My life"? It's on page 1 (it points to page 1). Where is the chapter "My job?" It's on page 23.

The pointer you usually see in Rust is called a reference. This is the important part to know: a reference points to the memory of another value. A reference means you borrow the value, but you don't own it. It's the same as our book: the table of contents doesn't own the information. It's the chapters that own the information. In Rust, references have a & in front of them. So:

  • let my_variable = 8 makes a regular variable, but
  • let my_reference = &my_variable makes a reference.

You read my_reference = &my_variable like this: "my_reference is a reference to my_variable". Or: "my_reference refers to my_variable".

This means that my_reference is only looking at the data of my_variable. my_variable still owns its data.

You can also have a reference to a reference, or any number of references.

fn main() {
    let my_number = 15; // This is an i32
    let single_reference = &my_number; //  This is a &i32
    let double_reference = &single_reference; // This is a &&i32
    let five_references = &&&&&my_number; // This is a &&&&&i32
}

These are all different types, just in the same way that "a friend of a friend" is different from "a friend".

More about printing

In Rust you can print things in almost any way you want. Here are some more things to know about printing.

Adding \n will make a new line, and \t will make a tab:

fn main() {
    // Note: this is print!, not println!
    print!("\t Start with a tab\nand move to a new line");
}

This prints:

         Start with a tab
and move to a new line

Inside "" you can write over many lines with no problem, but be careful with the spacing:

fn main() {
    // Note: After the first line you have to start on the far left.
    // If you write directly under println!, it will add the spaces
    println!("Inside quotes
you can write over
many lines
and it will print just fine.");

    println!("If you forget to write
    on the left side, the spaces
    will be added when you print.");
}

This prints:

Inside quotes
you can write over
many lines
and it will print just fine.
If you forget to write
    on the left side, the spaces
    will be added when you print.

If you want to print characters like \n (called "escape characters"), you can add an extra \:

fn main() {
    println!("Here are two escape characters: \\n and \\t");
}

This prints:

Here are two escape characters: \n and \t

Sometimes you have too many " and escape characters, and want Rust to ignore everything. To do this, you can add r# to the beginning and # to the end.

fn main() {
    println!("He said, \"You can find the file at c:\\files\\my_documents\\file.txt.\" Then I found the file."); // We used \ five times here
    println!(r#"He said, "You can find the file at c:\files\my_documents\file.txt." Then I found the file."#)
}

This prints the same thing, but using r# makes it easier for humans to read.

He said, "You can find the file at c:\files\my_documents\file.txt." Then I found the file.
He said, "You can find the file at c:\files\my_documents\file.txt." Then I found the file.

If you need to print with a # inside, then you can start with r## and end with ##. And if you need more than one, you can add one more # on each side.

Here are four examples:

fn main() {

    let my_string = "'Ice to see you,' he said."; // single quotes
    let quote_string = r#""Ice to see you," he said."#; // double quotes
    let hashtag_string = r##"The hashtag #IceToSeeYou had become very popular."##; // Has one # so we need at least ##
    let many_hashtags = r####""You don't have to type ### to use a hashtag. You can just use #.""####; // Has three ### so we need at least ####

    println!("{}\n{}\n{}\n{}\n", my_string, quote_string, hashtag_string, many_hashtags);

}

This will print:

'Ice to see you,' he said.
"Ice to see you," he said.
The hashtag #IceToSeeYou had become very popular.
"You don't have to type ### to use a hashtag. You can just use #."

r# has another use: with it you can use a keyword (words like let, fn, etc.) as a variable name.

fn main() {
    let r#let = 6; // The variable's name is let
    let mut r#mut = 10; // This variable's name is mut
}

r# has this function because older versions of Rust had fewer keywords than Rust now. So with r# you can avoid mistakes with variable names that were not keywords before.

Or maybe for some reason you really need a function to have a name like return. Then you can write this:

fn r#return() -> u8 {
    println!("Here is your number.");
    8
}

fn main() {
    let my_number = r#return();
    println!("{}", my_number);
}

This prints:

Here is your number.
8

So you probably won't need it, but if you really need to use a keyword for a variable then you can use r#.

If you want to print the bytes of a &str or a char, you can just write b before the string. This works for all ASCII characters. These are all the ASCII characters:

☺☻♥♦♣♠♫☼►◄↕‼¶§▬↨↑↓→∟↔▲▼123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

So when you print this:

fn main() {
    println!("{:?}", b"This will look like numbers");
}

Here is the result:

[84, 104, 105, 115, 32, 119, 105, 108, 108, 32, 108, 111, 111, 107, 32, 108, 105, 107, 101, 32, 110, 117, 109, 98, 101, 114, 115]

For a char this is called a byte, and for a &str it's called a byte string.

You can also put b and r together if you need to:

fn main() {
    println!("{:?}", br##"I like to write "#"."##);
}

That will print [73, 32, 108, 105, 107, 101, 32, 116, 111, 32, 119, 114, 105, 116, 101, 32, 34, 35, 34, 46].

There is also a Unicode escape that lets you print any Unicode character inside a string: \u{}. A hexadecimal number goes inside the {} to print it. Here is a short example of how to get the Unicode number, and how to print it again.

fn main() {
    println!("{:X}", '행' as u32); // Cast char as u32 to get the hexadecimal value
    println!("{:X}", 'H' as u32);
    println!("{:X}", '居' as u32);
    println!("{:X}", 'い' as u32);

    println!("\u{D589}, \u{48}, \u{5C45}, \u{3044}"); // Try printing them with unicode escape \u
}

We know that println! can print with {} (for Display) and {:?} (for Debug), plus {:#?} for pretty printing. But there are many other ways to print.

For example, if you have a reference, you can use {:p} to print the pointer address. Pointer address means the location in your computer's memory.

fn main() {
    let number = 9;
    let number_ref = &number;
    println!("{:p}", number_ref);
}

This prints 0xe2bc0ffcfc or some other address. It might be different every time, depending on where your computer stores it.

Or you can print binary, hexadecimal and octal:

fn main() {
    let number = 555;
    println!("Binary: {:b}, hexadecimal: {:x}, octal: {:o}", number, number, number);
}

This prints Binary: 1000101011, hexadecimal: 22b, octal: 1053.

Or you can add numbers to change the order. The first variable will be in index 0, the next in index 1, and so on.

fn main() {
    let father_name = "Vlad";
    let son_name = "Adrian Fahrenheit";
    let family_name = "Țepeș";
    println!("This is {1} {2}, son of {0} {2}.", father_name, son_name, family_name);
}

father_name is in position 0, son_name is in position 1, and family_name is in position 2. So it prints This is Adrian Fahrenheit Țepeș, son of Vlad Țepeș.

Maybe you have a very complex string to print with too many variables inside the {} curly brackets. Or maybe you need to print a variable more than one time. Then it can help to add names to the {}:

fn main() {
    println!(
        "{city1} is in {country} and {city2} is also in {country},
but {city3} is not in {country}.",
        city1 = "Seoul",
        city2 = "Busan",
        city3 = "Tokyo",
        country = "Korea"
    );
}

That will print:

Seoul is in Korea and Busan is also in Korea,
but Tokyo is not in Korea.

Very complex printing is also possible in Rust if you want to use it. Here is how to do it:

{variable:padding alignment minimum.maximum}

To understand this, look at the

  1. Do you want a variable name? Write that first, like when we wrote {country} above. (Then add a : after it if you want to do more things)
  2. Do you want a padding character? For example, 55 with three "padding zeros" looks like 00055.
  3. What alignment (left / middle / right) for the padding?
  4. Do you want a minimum length? (just write a number)
  5. Do you want a maximum length? (write a number with a . in front)

For example, if I want to write "a" with five ㅎ characters on the left and five ㅎ characters on the right:

fn main() {
    let letter = "a";
    println!("{:ㅎ^11}", letter);
}

This prints ㅎㅎㅎㅎㅎaㅎㅎㅎㅎㅎ. Let's look at 1) to 5) for this to understand how the compiler reads it.

  • Do you want a variable name? {:ㅎ^11} There is no variable name. There is nothing before :.
  • Do you want a padding character? {:ㅎ^11} Yes. ㅎ comes after the : and has a ^. < means padding with the character on the left, > means on the right, and ^ means in the middle.
  • Do you want a minimum length? {:ㅎ^11} Yes: there is an 11 after.
  • Do you want a maximum length? {:ㅎ^11} No: there is no number with a . before.

Here is an example of many types of formatting.

fn main() {
    let title = "TODAY'S NEWS";
    println!("{:-^30}", title); // no variable name, pad with -, put in centre, 30 characters long
    let bar = "|";
    println!("{: <15}{: >15}", bar, bar); // no variable name, pad with space, 15 characters each, one to the left, one to the right
    let a = "SEOUL";
    let b = "TOKYO";
    println!("{city1:-<15}{city2:->15}", city1 = a, city2 = b); // variable names city1 and city2, pad with -, one to the left, one to the right
}

It prints:

---------TODAY'S NEWS---------
|                            |
SEOUL--------------------TOKYO

Strings

See this chapter on YouTube

Rust has two main types of strings: String and &str. What is the difference?

  • &str is a simple string. When you write let my_variable = "Hello, world!", you create a &str. A &str is very fast.
  • String is a more complicated string. It is a bit slower, but it has more functions. A String is a pointer, with data on the heap.

Also note that &str has the & in front of it because you need a reference to use a str. That's because of the reason we saw above: the stack needs to know the size. So we give it a & that it knows the size of, and then it is happy. Also, because you use a & to interact with a str, you don't own it. But a String is an owned type. We will soon learn why that is important to know.

Both &str and String are UTF-8. For example, you can write:

fn main() {
    let name = "서태지"; // This is a Korean name. No problem, because a &str is UTF-8.
    let other_name = String::from("Adrian Fahrenheit Țepeș"); // Ț and ș are no problem in UTF-8.
}

You can see in String::from("Adrian Fahrenheit Țepeș") that it is easy to make a String from a &str. The two types are very closely linked together, even though they are different.

You can even write emojis, thanks to UTF-8.

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

On your computer that will print My name is actually 😂 unless your command line can't print it. Then it will show My name is actually �. But Rust has no problem with emojis or any other Unicode.

Let's look at the reason for using a & for strs again to make sure we understand.

  • str is a dynamically sized type (dynamically sized = the size can be different). For example, the names "서태지" and "Adrian Fahrenheit Țepeș" are not the same size:
fn main() {

    println!("A String is always {:?} bytes. It is Sized.", std::mem::size_of::<String>()); // std::mem::size_of::<Type>() gives you the size in bytes of a type
    println!("And an i8 is always {:?} bytes. It is Sized.", std::mem::size_of::<i8>());
    println!("And an f64 is always {:?} bytes. It is Sized.", std::mem::size_of::<f64>());
    println!("But a &str? It can be anything. '서태지' is {:?} bytes. It is not Sized.", std::mem::size_of_val("서태지")); // std::mem::size_of_val() gives you the size in bytes of a variable
    println!("And 'Adrian Fahrenheit Țepeș' is {:?} bytes. It is not Sized.", std::mem::size_of_val("Adrian Fahrenheit Țepeș"));
}

This prints:

A String is always 24 bytes. It is Sized.
And an i8 is always 1 bytes. It is Sized.
And an f64 is always 8 bytes. It is Sized.
But a &str? It can be anything. '서태지' is 9 bytes. It is not Sized.
And 'Adrian Fahrenheit Țepeș' is 25 bytes. It is not Sized.

That is why we need a &, because & makes a pointer, and Rust knows the size of the pointer. So the pointer goes on the stack. If we wrote str, Rust wouldn't know what to do because it doesn't know the size.

There are many ways to make a String. Here are some:

  • String::from("This is the string text"); This is a method for String that takes text and creates a String.
  • "This is the string text".to_string(). This is a method for &str that makes it a String.
  • The format! macro. This is like println! except it creates a String instead of printing. So you can do this:
fn main() {
    let my_name = "Billybrobby";
    let my_country = "USA";
    let my_home = "Korea";

    let together = format!(
        "I am {} and I come from {} but I live in {}.",
        my_name, my_country, my_home
    );
}

Now we have a String named together, but did not print it yet.

One other way to make a String is called .into() but it is a bit different because .into() isn't just for making a String. Some types can easily convert to and from another type using From and .into(). And if you have From, then you also have .into(). From is clearer because you already know the types: you know that String::from("Some str") is a String from a &str. But with .into(), sometimes the compiler doesn't know:

fn main() {
    let my_string = "Try to make this a String".into(); // ⚠️
}

Rust doesn't know what type you want, because many types can be made from a &str. It says, "I can make a &str into a lot of things. Which one do you want?"

error[E0282]: type annotations needed
 --> src\main.rs:2:9
  |
2 |     let my_string = "Try to make this a String".into();
  |         ^^^^^^^^^ consider giving `my_string` a type

So you can do this:

fn main() {
    let my_string: String = "Try to make this a String".into();
}

And now you get a String.

const and static

See this chapter on YouTube

There are two other ways to declare values, not just with let. These are const and static. Also, Rust won't use type inference: you need to write the type for them. These are for values that don't change (const means constant). The difference is that:

  • const is for values that don't change, the name is replaced with the value when it's used,
  • static is similar to const, but has a fixed memory location and can act as a global variable.

So they are almost the same. Rust programmers almost always use const.

You write them with ALL CAPITAL LETTERS, and usually outside of main so that they can live for the whole program.

Two examples are: const NUMBER_OF_MONTHS: u32 = 12; and static SEASONS: [&str; 4] = ["Spring", "Summer", "Fall", "Winter"];

More on references

See this chapter on YouTube

References are very important in Rust. Rust uses references to make sure that all memory access is safe. We know that we use & to create a reference:

fn main() {
    let country = String::from("Austria");
    let ref_one = &country;
    let ref_two = &country;

    println!("{}", ref_one);
}

This prints Austria.

In the code, country is a String. We then created two references to country. They have the type &String, which you say is a "reference to a String". We could create three references or one hundred references to country and it would be no problem.

But this is a problem:

fn return_str() -> &str {
    let country = String::from("Austria");
    let country_ref = &country;
    country_ref // ⚠️
}

fn main() {
    let country = return_str();
}

The function return_str() creates a String, then it creates a reference to the String. Then it tries to return the reference. But the String country only lives inside the function, and then it dies. Once a variable is gone, the computer will clean up the memory and use it for something else. So after the function is over, country_ref is referring to memory that is already gone, and that's not okay. Rust prevents us from making a mistake with memory here.

This is the important part about the "owned" type that we talked about above. Because you own a String, you can pass it around. But a &String will die if its String dies, so you don't pass around "ownership" with it.

Mutable references

See this chapter on YouTube

If you want to use a reference to change data, you can use a mutable reference. For a mutable reference, you write &mut instead of &.

fn main() {
    let mut my_number = 8; // don't forget to write mut here!
    let num_ref = &mut my_number;
}

So what are the two types? my_number is an i32, and num_ref is &mut i32 (we say a "mutable reference to an i32").

So let's use it to add 10 to my_number. But you can't write num_ref += 10, because num_ref is not the i32 value, it is a &i32. The value is actually inside the i32. To reach the place where the value is, we use *. * means "I don't want the reference, I want the value behind the reference". In other words, one * is the opposite of &. Also, one * erases one &.

fn main() {
    let mut my_number = 8;
    let num_ref = &mut my_number;
    *num_ref += 10; // Use * to change the i32 value.
    println!("{}", my_number);

    let second_number = 800;
    let triple_reference = &&&second_number;
    println!("Second_number = triple_reference? {}", second_number == ***triple_reference);
}

This prints:

18
Second_number = triple_reference? true

Because using & is called "referencing", using * is called "dereferencing".

Rust has two rules for mutable and immutable references. They are very important, but also easy to remember because they make sense.

  • Rule 1: If you have only immutable references, you can have as many as you want. 1 is fine, 3 is fine, 1000 is fine. No problem.
  • Rule 2: If you have a mutable reference, you can only have one. Also, you can't have an immutable reference and a mutable reference together.

This is because mutable references can change the data. You could get problems if you change the data when other references are reading it.

A good way to understand is to think of a Powerpoint presentation.

Situation one is about only one mutable reference.

Situation one: An employee is writing a Powerpoint presentation. He wants his manager to help him. The employee gives his login information to his manager, and asks him to help by making edits. Now the manager has a "mutable reference" to the employee's presentation. The manager can make any changes he wants, and give the computer back later. This is fine, because nobody else is looking at the presentation.

Situation two is about only immutable references.

Situation two: The employee is giving the presentation to 100 people. All 100 people can now see the employee's data. They all have an "immutable reference" to the employee's presentation. This is fine, because they can see it but nobody can change the data.

Situation three is the problem situation.

Situation three: The Employee gives his manager his login information. His manager now has a "mutable reference". Then the employee went to give the presentation to 100 people, but the manager can still login. This is not fine, because the manager can log in and do anything. Maybe his manager will log into the computer and start typing an email to his mother! Now the 100 people have to watch the manager write an email to his mother instead of the presentation. That's not what they expected to see.

Here is an example of a mutable borrow with an immutable borrow:

fn main() {
    let mut number = 10;
    let number_ref = &number;
    let number_change = &mut number;
    *number_change += 10;
    println!("{}", number_ref); // ⚠️
}

The compiler prints a helpful message to show us the problem.

error[E0502]: cannot borrow `number` as mutable because it is also borrowed as immutable
 --> src\main.rs:4:25
  |
3 |     let number_ref = &number;
  |                      ------- immutable borrow occurs here
4 |     let number_change = &mut number;
  |                         ^^^^^^^^^^^ mutable borrow occurs here
5 |     *number_change += 10;
6 |     println!("{}", number_ref);
  |                    ---------- immutable borrow later used here

However, this code will work. Why?

fn main() {
    let mut number = 10;
    let number_change = &mut number; // create a mutable reference
    *number_change += 10; // use mutable reference to add 10
    let number_ref = &number; // create an immutable reference
    println!("{}", number_ref); // print the immutable reference
}

It prints 20 with no problem. It works because the compiler is smart enough to understand our code. It knows that we used number_change to change number, but didn't use it again. So here there is no problem. We are not using immutable and mutable references together.

Earlier in Rust this kind of code actually generated an error, but the compiler is smarter now. It can understand not just what we type, but how we use everything.

Shadowing again

Remember when we said that shadowing doesn't destroy a value but blocks it? Now we can use references to see this.

fn main() {
    let country = String::from("Austria");
    let country_ref = &country;
    let country = 8;
    println!("{}, {}", country_ref, country);
}

Does this print Austria, 8 or 8, 8? It prints Austria, 8. First we declare a String called country. Then we create a reference country_ref to this string. Then we shadow country with 8, which is an i32. But the first country was not destroyed, so country_ref still says "Austria", not "8". Here is the same code with some comments to show how it works:

fn main() {
    let country = String::from("Austria"); // Now we have a String called country
    let country_ref = &country; // country_ref is a reference to this data. It's not going to change
    let country = 8; // Now we have a variable called country that is an i8. But it has no relation to the other one, or to country_ref
    println!("{}, {}", country_ref, country); // country_ref still refers to the data of String::from("Austria") that we gave it.
}

Giving references to functions

See this chapter on YouTube: immutable references and mutable references

References are very useful for functions. The rule in Rust on values is: a value can only have one owner.

This code will not work:

fn print_country(country_name: String) {
    println!("{}", country_name);
}

fn main() {
    let country = String::from("Austria");
    print_country(country); // We print "Austria"
    print_country(country); // ⚠️ That was fun, let's do it again!
}

It does not work because country is destroyed. Here's how:

  • Step 1: We create the String called country. country is the owner.
  • Step 2: We give country to print_country. print_country doesn't have an ->, so it doesn't return anything. After print_country finishes, our String is now dead.
  • Step 3: We try to give country to print_country, but we already did that. We don't have country to give anymore.

We can make print_country give the String back, but it is a bit awkward.

fn print_country(country_name: String) -> String {
    println!("{}", country_name);
    country_name // return it here
}

fn main() {
    let country = String::from("Austria");
    let country = print_country(country); // we have to use let here now to get the String back
    print_country(country);
}

Now it prints:

Austria
Austria

The much better way to fix this is by adding &.

fn print_country(country_name: &String) {
    println!("{}", country_name);
}

fn main() {
    let country = String::from("Austria");
    print_country(&country); // We print "Austria"
    print_country(&country); // That was fun, let's do it again!
}

Now print_country() is a function that takes a reference to a String: a &String. Also, we give it a reference to country by writing &country. This says "you can look at it, but I will keep it".

Now let's do something similar with a mutable reference. Here is an example of a function that uses a mutable variable.

fn add_hungary(country_name: &mut String) { // first we say that the function takes a mutable reference
    country_name.push_str("-Hungary"); // push_str() adds a &str to a String
    println!("Now it says: {}", country_name);
}

fn main() {
    let mut country = String::from("Austria");
    add_hungary(&mut country); // we also need to give it a mutable reference.
}

This prints Now it says: Austria-Hungary.

So to conclude:

  • fn function_name(variable: String) takes a String and owns it. If it doesn't return anything, then the variable dies inside the function.
  • fn function_name(variable: &String) borrows a String and can look at it
  • fn function_name(variable: &mut String) borrows a String and can change it

Here is an example that looks like a mutable reference, but it is different.

fn main() {
    let country = String::from("Austria"); // country is not mutable, but we are going to print Austria-Hungary. How?
    adds_hungary(country);
}

fn adds_hungary(mut country: String) { // Here's how: adds_hungary takes the String and declares it mutable!
    country.push_str("-Hungary");
    println!("{}", country);
}

How is this possible? It is because mut country is not a reference: adds_hungary owns country now. (Remember, it takes String and not &String). The moment you call adds_hungary, it becomes the full owner. country has nothing to do with String::from("Austria") anymore. So adds_hungary can take country as mutable, and it is perfectly safe to do so.

Remember our employee Powerpoint and manager situation above? In this situation it is like the employee just giving his whole computer to the manager. The employee won't ever touch it again, so the manager can do anything he wants to it.

Copy types

Some types in Rust are very simple. They are called copy types. These simple types are all on the stack, and the compiler knows their size. That means that they are very easy to copy, so the compiler always copies when you send it to a function. It always copies because they are so small and easy that there is no reason not to copy. So you don't need to worry about ownership for these types.

These simple types include: integers, floats, booleans (true and false), and char.

How do you know if a type implements copy? (implements = can use) You can check the documentation. For example, here is the documentation for char:

https://doc.rust-lang.org/std/primitive.char.html

On the left you can see Trait Implementations. You can see for example Copy, Debug, and Display. So you know that a char:

  • is copied when you send it to a function (Copy)
  • can use {} to print (Display)
  • can use {:?} to print (Debug)
fn prints_number(number: i32) { // There is no -> so it's not returning anything
                             // If number was not copy type, it would take it
                             // and we couldn't use it again
    println!("{}", number);
}

fn main() {
    let my_number = 8;
    prints_number(my_number); // Prints 8. prints_number gets a copy of my_number
    prints_number(my_number); // Prints 8 again.
                              // No problem, because my_number is copy type!
}

But if you look at the documentation for String, it is not copy type.

https://doc.rust-lang.org/std/string/struct.String.html

On the left in Trait Implementations you can look in alphabetical order. A, B, C... there is no Copy in C. But there is Clone. Clone is similar to Copy, but usually needs more memory. Also, you have to call it with .clone() - it won't clone just by itself.

In this example, prints_country() prints the country name, a String. We want to print it two times, but we can't:

fn prints_country(country_name: String) {
    println!("{}", country_name);
}

fn main() {
    let country = String::from("Kiribati");
    prints_country(country);
    prints_country(country); // ⚠️
}

But now we understand the message.

error[E0382]: use of moved value: `country`
 --> src\main.rs:4:20
  |
2 |     let country = String::from("Kiribati");
  |         ------- move occurs because `country` has type `std::string::String`, which does not implement the `Copy` trait
3 |     prints_country(country);
  |                    ------- value moved here
4 |     prints_country(country);
  |                    ^^^^^^^ value used here after move

The important part is which does not implement the Copy trait. But in the documentation we saw that String implements the Clone trait. So we can add .clone() to our code. This creates a clone, and we send the clone to the function. Now country is still alive, so we can use it.

fn prints_country(country_name: String) {
    println!("{}", country_name);
}

fn main() {
    let country = String::from("Kiribati");
    prints_country(country.clone()); // make a clone and give it to the function. Only the clone goes in, and country is still alive
    prints_country(country);
}

Of course, if the String is very large, .clone() can use a lot of memory. One String can be a whole book in length, and every time we call .clone() it will copy the book. So using & for a reference is faster, if you can. For example, this code pushes a &str onto a String and then makes a clone every time it gets used in a function:

fn get_length(input: String) { // Takes ownership of a String
    println!("It's {} words long.", input.split_whitespace().count()); // splits to count the number of words
}

fn main() {
    let mut my_string = String::new();
    for _ in 0..50 {
        my_string.push_str("Here are some more words "); // push the words on
        get_length(my_string.clone()); // gives it a clone every time
    }
}

It prints:

It's 5 words long.
It's 10 words long.
...
It's 250 words long.

That's 50 clones. Here it is using a reference instead, which is better:

fn get_length(input: &String) {
    println!("It's {} words long.", input.split_whitespace().count());
}

fn main() {
    let mut my_string = String::new();
    for _ in 0..50 {
        my_string.push_str("Here are some more words ");
        get_length(&my_string);
    }
}

Instead of 50 clones, it's zero.

Variables without values

A variable without a value is called an "uninitialized" variable. Uninitialized means "hasn't started yet". They are simple: just write let and the variable name:

fn main() {
    let my_variable; // ⚠️
}

But you can't use it yet, and Rust won't compile if anything is uninitialized.

But sometimes they can be useful. A good example is when:

  • You have a code block and the value for your variable is inside it, and
  • The variable needs to live outside of the code block.
fn loop_then_return(mut counter: i32) -> i32 {
    loop {
        counter += 1;
        if counter % 50 == 0 {
            break;
        }
    }
    counter
}

fn main() {
    let my_number;

    {
        // Pretend we need to have this code block
        let number = {
            // Pretend there is code here to make a number
            // Lots of code, and finally:
            57
        };

        my_number = loop_then_return(number);
    }

    println!("{}", my_number);
}

This prints 100.

You can see that my_number was declared in the main() function, so it lives until the end. But it gets its value from inside a loop. However, that value lives as long as my_number, because my_number has the value. And if you wrote let my_number = loop_then_return(number) inside the block, it would just die right away.

It helps to imagine if you simplify the code. loop_then_return(number) gives the result 100, so let's delete it and write 100 instead. Also, now we don't need number so we will delete it too. Now it looks like this:

fn main() {
    let my_number;
    {
        my_number = 100;
    }

    println!("{}", my_number);
}

So it's almost like saying let my_number = { 100 };.

Also note that my_number is not mut. It didn't get a value until it got 100, so it never changed its value. In the end, the real code for my_number is just let my_number = 100;.

Collection types

Rust has a lot of types for making a collection. Collections are for when you need more than one value in one spot. For example, you could have information on all the cities in your country inside one variable. We will start with arrays, which are fastest but also have the least functionality. They are kind of like &str in that way.

Arrays

An array is data inside square brackets: []. Arrays:

  • must not change their size,
  • must only contain the same type.

They are very fast, however.

The type of an array is: [type; number]. For example, the type of ["One", "Two"] is [&str; 2]. This means that even these two arrays have different types:

fn main() {
    let array1 = ["One", "Two"]; // This one is type [&str; 2]
    let array2 = ["One", "Two", "Five"]; // But this one is type [&str; 3]. Different type!
}

Here is a good tip: to know the type of a variable, you can "ask" the compiler by giving it bad instructions. For example:

fn main() {
    let seasons = ["Spring", "Summer", "Autumn", "Winter"];
    let seasons2 = ["Spring", "Summer", "Fall", "Autumn", "Winter"];
    seasons.ddd(); // ⚠️
    seasons2.thd(); // ⚠️ as well
}

The compiler says, "What? There's no .ddd() method for seasons and no .thd() method for seasons 2 either!!" as you can see:

error[E0599]: no method named `ddd` found for array `[&str; 4]` in the current scope
 --> src\main.rs:4:13
  |
4 |     seasons.ddd(); // 
  |             ^^^ method not found in `[&str; 4]`

error[E0599]: no method named `thd` found for array `[&str; 5]` in the current scope
 --> src\main.rs:5:14
  |
5 |     seasons2.thd(); // 
  |              ^^^ method not found in `[&str; 5]`

So it tells you method not found in `[&str; 4]`, which is the type.

If you want an array with all the same value, you can declare it like this:

fn main() {
    let my_array = ["a"; 10];
    println!("{:?}", my_array);
}

This prints ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"].

This method is used a lot to create buffers. For example, let mut buffer = [0; 640] creates an array of 640 zeroes. Then we can change zero to other numbers in order to add data.

You can index (get) entries in an array with []. The first entry is [0], the second is [1], and so on.

fn main() {
    let my_numbers = [0, 10, -20];
    println!("{}", my_numbers[1]); // prints 10
}

You can get a slice (a piece) of an array. First you need a &, because the compiler doesn't know the size. Then you can use .. to show the range.

For example, let's use this array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

fn main() {
    let array_of_ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let three_to_five = &array_of_ten[2..5];
    let start_at_two = &array_of_ten[1..];
    let end_at_five = &array_of_ten[..5];
    let everything = &array_of_ten[..];

    println!("Three to five: {:?}, start at two: {:?}, end at five: {:?}, everything: {:?}", three_to_five, start_at_two, end_at_five, everything);
}

Remember that:

  • Index numbers start at 0 (not 1)
  • Index ranges are exclusive (they do not include the last number)

So [0..2] means the first index and the second index (0 and 1). Or you can call it the "zeroth and first" index. It doesn't have the third item, which is index 2.

You can also have an inclusive range, which means it includes the last number too. To do this, add = to write ..= instead of ... So instead of [0..2] you can write [0..=2] if you want the first, second, and third item.

Vectors

See this chapter on YouTube

In the same way that we have &str and String, we have arrays and vectors. Arrays are faster with less functionality, and vectors are slower with more functionality. (Of course, Rust is always very fast so vectors are not slow, just slower than arrays.) The type is written Vec, and you can also just call it a "vec".

There are two main ways to declare a vector. One is like with String using new:

fn main() {
    let name1 = String::from("Windy");
    let name2 = String::from("Gomesy");

    let mut my_vec = Vec::new();
    // If we run the program now, the compiler will give an error.
    // It doesn't know the type of vec.

    my_vec.push(name1); // Now it knows: it's Vec<String>
    my_vec.push(name2);
}

You can see that a Vec always has something else inside it, and that's what the <> (angle brackets) are for. A Vec<String> is a vector with one or more Strings. You can also have more types inside. For example:

  • Vec<(i32, i32)> this is a Vec where each item is a tuple: (i32, i32).
  • Vec<Vec<String>> this is a Vec that has Vecs of Strings. Say for example you wanted to save your favourite book as a Vec<String>. Then you do it again with another book, and get another Vec<String>. To hold both books, you would put them into another Vec and that would be a Vec<Vec<String>>.

Instead of using .push() to make Rust decide the type, you can just declare the type.

fn main() {
    let mut my_vec: Vec<String> = Vec::new(); // The compiler knows the type
                                              // so there is no error.
}

You can see that items in vectors must have the same type.

Another easy way to create a vector is with the vec! macro. It looks like an array declaration, but has vec! in front of it.

fn main() {
    let mut my_vec = vec![8, 10, 10];
}

The type is Vec<i32>. You call it a "Vec of i32s". And a Vec<String> is a "Vec of strings". And a Vec<Vec<String>> is a "Vec of a vec of strings".

You can slice a vector too, just like in an array.

fn main() {
    let vec_of_ten = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    // Everything is the same as above except we added vec!.
    let three_to_five = &vec_of_ten[2..5];
    let start_at_two = &vec_of_ten[1..];
    let end_at_five = &vec_of_ten[..5];
    let everything = &vec_of_ten[..];

    println!("Three to five: {:?},
start at two: {:?}
end at five: {:?}
everything: {:?}", three_to_five, start_at_two, end_at_five, everything);
}

Because a vec is slower than an array, we can use some methods to make it faster. A vec has a capacity, which means the space given to the vector. When you push a new item on the vector, it gets closer and closer to the capacity. Then if you go past the capacity, it will make its capacity double and copy the items into the new space. This is called reallocation. We'll use a method called .capacity() to look at the capacity of a vector as we add items to it.

For example:

fn main() {
    let mut num_vec = Vec::new();
    println!("{}", num_vec.capacity()); // 0 elements: prints 0
    num_vec.push('a'); // add one character
    println!("{}", num_vec.capacity()); // 1 element: prints 4. Vecs with 1 item always start with capacity 4
    num_vec.push('a'); // add one more
    num_vec.push('a'); // add one more
    num_vec.push('a'); // add one more
    println!("{}", num_vec.capacity()); // 4 elements: still prints 4.
    num_vec.push('a'); // add one more
    println!("{}", num_vec.capacity()); // prints 8. We have 5 elements, but it doubled 4 to 8 to make space
}

This prints:

0
4
4
8

So this vector has two reallocations: 0 to 4, and 4 to 8. We can make it faster:

fn main() {
    let mut num_vec = Vec::with_capacity(8); // Give it capacity 8
    num_vec.push('a'); // add one character
    println!("{}", num_vec.capacity()); // prints 8
    num_vec.push('a'); // add one more
    println!("{}", num_vec.capacity()); // prints 8
    num_vec.push('a'); // add one more
    println!("{}", num_vec.capacity()); // prints 8.
    num_vec.push('a'); // add one more
    num_vec.push('a'); // add one more // Now we have 5 elements
    println!("{}", num_vec.capacity()); // Still 8
}

This vector has 0 reallocations, which is better. So if you think you know how many elements you need, you can use Vec::with_capacity() to make it faster.

You remember that you can use .into() to make a &str into a String. You can also use it to make an array into a Vec. You have to tell .into() that you want a Vec, but you don't have to choose the type of Vec. If you don't want to choose, you can write Vec<_>.

fn main() {
    let my_vec: Vec<u8> = [1, 2, 3].into();
    let my_vec2: Vec<_> = [9, 0, 10].into(); // Vec<_> means "choose the Vec type for me"
                                             // Rust will choose Vec<i32>
}

Tuples

See this chapter on YouTube

Tuples in Rust use (). We have seen many empty tuples already, because nothing in a function actually means an empty tuple:

fn do_something() {}

is actually short for:

fn do_something() -> () {}

That function gets nothing (an empty tuple), and returns nothing (an empty tuple). So we have been using tuples a lot already. When you don't return anything in a function, you actually return an empty tuple.

fn just_prints() {
    println!("I am printing"); // Adding ; means we return an empty tuple
}

fn main() {}

But tuples can hold many things, and can hold different types too. Items inside a tuple are also indexed with numbers 0, 1, 2, and so on. But to access them, you use a . instead of a []. Let's put a whole bunch of types into a single tuple.

fn main() {
    let random_tuple = ("Here is a name", 8, vec!['a'], 'b', [8, 9, 10], 7.7);
    println!(
        "Inside the tuple is: First item: {:?}
Second item: {:?}
Third item: {:?}
Fourth item: {:?}
Fifth item: {:?}
Sixth item: {:?}",
        random_tuple.0,
        random_tuple.1,
        random_tuple.2,
        random_tuple.3,
        random_tuple.4,
        random_tuple.5,
    )
}

This prints:

Inside the tuple is: First item: "Here is a name"
Second item: 8
Third item: ['a']
Fourth item: 'b'
Fifth item: [8, 9, 10]
Sixth item: 7.7

That tuple is of type (&str, i32, Vec<char>, char, [i32; 3], f64).

You can use a tuple to create multiple variables. Take a look at this code:

fn main() {
    let str_vec = vec!["one", "two", "three"];
}

str_vec has three items in it. What if we want to pull them out? That's where we can use a tuple.

fn main() {
    let str_vec = vec!["one", "two", "three"];

    let (a, b, c) = (str_vec[0], str_vec[1], str_vec[2]); // call them a, b, and c
    println!("{:?}", b);
}

That prints "two", which is what b is. This is called destructuring. That is because first the variables are inside a structure, but then we made a, b, and c that are not inside a structure.

If you need to destructure but don't want all the variables, you can use _.

fn main() {
    let str_vec = vec!["one", "two", "three"];

    let (_, _, variable) = (str_vec[0], str_vec[1], str_vec[2]);
}

Now it only creates a variable called variable but doesn't make a variable for the others.

There are many more collection types, and many more ways to use arrays, vecs, and tuples. We will learn more about them too, but first we will learn control flow.

Control flow

See this chapter on YouTube: Part 1 and Part 2

Control flow means telling your code what to do in different situations. The simplest control flow is if.

fn main() {
    let my_number = 5;
    if my_number == 7 {
        println!("It's seven");
    }
}

Also note that you use == and not =. == is to compare, = is to assign (to give a value). Also note that we wrote if my_number == 7 and not if (my_number == 7). You don't need brackets with if in Rust.

else if and else give you more control:

fn main() {
    let my_number = 5;
    if my_number == 7 {
        println!("It's seven");
    } else if my_number == 6 {
        println!("It's six")
    } else {
        println!("It's a different number")
    }
}

This prints It's a different number because it's not equal to 7 or 6.

You can add more conditions with && (and) and || (or).

fn main() {
    let my_number = 5;
    if my_number % 2 == 1 && my_number > 0 { // % 2 means the number that remains after diving by two
        println!("It's a positive odd number");
    } else if my_number == 6 {
        println!("It's six")
    } else {
        println!("It's a different number")
    }
}

This prints It's a positive odd number because when you divide it by 2 you have a remainder of 1, and it's greater than 0.

You can see that too much if, else, and else if can be difficult to read. In this case you can use match instead, which looks much cleaner. But you must match for every possible result. For example, this will not work:

fn main() {
    let my_number: u8 = 5;
    match my_number {
        0 => println!("it's zero"),
        1 => println!("it's one"),
        2 => println!("it's two"),
        // ⚠️
    }
}

The compiler says:

error[E0004]: non-exhaustive patterns: `3u8..=std::u8::MAX` not covered
 --> src\main.rs:3:11
  |
3 |     match my_number {
  |           ^^^^^^^^^ pattern `3u8..=std::u8::MAX` not covered

This means "you told me about 0 to 2, but u8s can go up to 255. What about 3? What about 4? What about 5?" And so on. So you can add _ which means "anything else".

fn main() {
    let my_number: u8 = 5;
    match my_number {
        0 => println!("it's zero"),
        1 => println!("it's one"),
        2 => println!("it's two"),
        _ => println!("It's some other number"),
    }
}

That prints It's some other number.

Remember this for match:

  • You write match and then make a {} code block.
  • Write the pattern on the left and use a => fat arrow to say what to do when it matches.
  • Each line is called an "arm".
  • Put a comma between the arms (not a semicolon).

You can declare a value with a match:

fn main() {
    let my_number = 5;
    let second_number = match my_number {
        0 => 0,
        5 => 10,
        _ => 2,
    };
}

second_number will be 10. Do you see the semicolon at the end? That is because, after the match is over, we actually told the compiler this: let second_number = 10;

You can match on more complicated things too. You use a tuple to do it.

fn main() {
    let sky = "cloudy";
    let temperature = "warm";

    match (sky, temperature) {
        ("cloudy", "cold") => println!("It's dark and unpleasant today"),
        ("clear", "warm") => println!("It's a nice day"),
        ("cloudy", "warm") => println!("It's dark but not bad"),
        _ => println!("Not sure what the weather is."),
    }
}

This prints It's dark but not bad because it matches "cloudy" and "warm" for sky and temperature.

You can even put if inside of match. This is called a "match guard":

fn main() {
    let children = 5;
    let married = true;

    match (children, married) {
        (children, married) if married == false => println!("Not married with {} children", children),
        (children, married) if children == 0 && married == true => println!("Married but no children"),
        _ => println!("Married? {}. Number of children: {}.", married, children),
    }
}

This will print Married? true. Number of children: 5.

You can use _ as many times as you want in a match. In this match on colours, we have three but only check one at a time.

fn match_colours(rbg: (i32, i32, i32)) {
    match rbg {
        (r, _, _) if r < 10 => println!("Not much red"),
        (_, b, _) if b < 10 => println!("Not much blue"),
        (_, _, g) if g < 10 => println!("Not much green"),
        _ => println!("Each colour has at least 10"),
    }
}

fn main() {
    let first = (200, 0, 0);
    let second = (50, 50, 50);
    let third = (200, 50, 0);

    match_colours(first);
    match_colours(second);
    match_colours(third);

}

This prints:

Not much blue
Each colour has at least 10
Not much green

This also shows how match statements work, because in the first example it only printed Not much blue. But first also has not much green. A match statement always stops when it finds a match, and doesn't check the rest. This is a good example of code that compiles well but is not the code you want.

You can make a really big match statement to fix it, but it is probably better to use a for loop. We will talk about loops soon.

A match has to return the same type. So you can't do this:

fn main() {
    let my_number = 10;
    let some_variable = match my_number {
        10 => 8,
        _ => "Not ten", // ⚠️
    };
}

The compiler tells you that:

error[E0308]: `match` arms have incompatible types
  --> src\main.rs:17:14
   |
15 |       let some_variable = match my_number {
   |  _________________________-
16 | |         10 => 8,
   | |               - this is found to be of type `{integer}`
17 | |         _ => "Not ten",
   | |              ^^^^^^^^^ expected integer, found `&str`
18 | |     };
   | |_____- `match` arms have incompatible types

This will also not work, for the same reason:

fn main() {
    let some_variable = if my_number == 10 { 8 } else { "something else "}; // ⚠️
}

But this works, because it's not a match so you have a different let statement each time:

fn main() {
    let my_number = 10;

    if my_number == 10 {
        let some_variable = 8;
    } else {
        let some_variable = "Something else";
    }
}

You can also use @ to give a name to the value of a match expression, and then you can use it. In this example we match an i32 input in a function. If it's 4 or 13 we want to use that number in a println! statement. Otherwise, we don't need to use it.

fn match_number(input: i32) {
    match input {
    number @ 4 => println!("{} is an unlucky number in China (sounds close to 死)!", number),
    number @ 13 => println!("{} is unlucky in North America, lucky in Italy! In bocca al lupo!", number),
    _ => println!("Looks like a normal number"),
    }
}

fn main() {
    match_number(50);
    match_number(13);
    match_number(4);
}

This prints:

Looks like a normal number
13 is unlucky in North America, lucky in Italy! In bocca al lupo!
4 is an unlucky number in China (sounds close to 死)!

Structs

See this chapter on YouTube: Part 1 and Part 2

With structs, you can create your own type. You will use structs all the time in Rust because they are so convenient. Structs are created with the keyword struct. The name of a struct should be in UpperCamelCase (capital letter for each word, no spaces). If you write a struct in all lowercase, the compiler will tell you.

There are three types of structs. One is a "unit struct". Unit means "doesn't have anything". For a unit struct, you just write the name and a semicolon.

struct FileDirectory;
fn main() {}

The next is a tuple struct, or an unnamed struct. It is "unnamed" because you only need to write the types, not the field names. Tuple structs are good when you need a simple struct and don't need to remember names.

struct Colour(u8, u8, u8);

fn main() {
    let my_colour = Colour(50, 0, 50); // Make a colour out of RGB (red, green, blue)
    println!("The second part of the colour is: {}", my_colour.1);
}

This prints The second part of the colour is: 0.

The third type is the named struct. This is probably the most common struct. In this struct you declare field names and types inside a {} code block. Note that you don't write a semicolon after a named struct, because there is a whole code block after it.

struct Colour(u8, u8, u8); // Declare the same Colour tuple struct

struct SizeAndColour {
    size: u32,
    colour: Colour, // And we put it in our new named struct
}

fn main() {
    let my_colour = Colour(50, 0, 50);

    let size_and_colour = SizeAndColour {
        size: 150,
        colour: my_colour
    };
}

You separate fields by commas in a named struct too. For the last field you can add a comma or not - it's up to you. SizeAndColour had a comma after colour:

struct Colour(u8, u8, u8); // Declare the same Colour tuple struct

struct SizeAndColour {
    size: u32,
    colour: Colour, // And we put it in our new named struct
}

fn main() {}

but you don't need it. But it can be a good idea to always put a comma, because sometimes you will change the order of the fields:

struct Colour(u8, u8, u8); // Declare the same Colour tuple struct

struct SizeAndColour {
    size: u32,
    colour: Colour // No comma here
}

fn main() {}

Then we decide to change the order...

struct SizeAndColour {
    colour: Colour // ⚠️ Whoops! Now this doesn't have a comma.
    size: u32,
}

fn main() {}

But it is not very important either way so you can choose whether to use a comma or not.

Let's create a Country struct to give an example. The Country struct has the fields population, capital, and leader_name.

struct Country {
    population: u32,
    capital: String,
    leader_name: String
}

fn main() {
    let population = 500_000;
    let capital = String::from("Elista");
    let leader_name = String::from("Batu Khasikov");

    let kalmykia = Country {
        population: population,
        capital: capital,
        leader_name: leader_name,
    };
}

Did you notice that we wrote the same thing twice? We wrote population: population, capital: capital, and leader_name: leader_name. Actually, you don't need to do that. If the field name and variable name are the same, you don't have to write it twice.

struct Country {
    population: u32,
    capital: String,
    leader_name: String
}

fn main() {
    let population = 500_000;
    let capital = String::from("Elista");
    let leader_name = String::from("Batu Khasikov");

    let kalmykia = Country {
        population,
        capital,
        leader_name,
    };
}

Enums

See this chapter on YouTube: Part 1, Part 2, Part 3 and Part 4

An enum is short for enumerations. They look very similar to a struct, but are different. Here is the difference:

  • Use a struct when you want one thing AND another thing.
  • Use an enum when you want one thing OR another thing.

So structs are for many things together, while enums are for many choices together.

To declare an enum, write enum and use a code block with the options, separated by commas. Just like a struct, the last part can have a comma or not. We will create an enum called ThingsInTheSky:

enum ThingsInTheSky {
    Sun,
    Stars,
}

fn main() {}

This is an enum because you can either see the sun, or the stars: you have to choose one. These are called variants.

// create the enum with two choices
enum ThingsInTheSky {
    Sun,
    Stars,
}

// With this function we can use an i32 to create ThingsInTheSky.
fn create_skystate(time: i32) -> ThingsInTheSky {
    match time {
        6..=18 => ThingsInTheSky::Sun, // Between 6 and 18 hours we can see the sun
        _ => ThingsInTheSky::Stars, // Otherwise, we can see stars
    }
}

// With this function we can match against the two choices in ThingsInTheSky.
fn check_skystate(state: &ThingsInTheSky) {
    match state {
        ThingsInTheSky::Sun => println!("I can see the sun!"),
        ThingsInTheSky::Stars => println!("I can see the stars!")
    }
}

fn main() {
    let time = 8; // it's 8 o'clock
    let skystate = create_skystate(time); // create_skystate returns a ThingsInTheSky
    check_skystate(&skystate); // Give it a reference so it can read the variable skystate
}

This prints I can see the sun!.

You can add data to an enum too.

enum ThingsInTheSky {
    Sun(String), // Now each variant has a string
    Stars(String),
}

fn create_skystate(time: i32) -> ThingsInTheSky {
    match time {
        6..=18 => ThingsInTheSky::Sun(String::from("I can see the sun!")), // Write the strings here
        _ => ThingsInTheSky::Stars(String::from("I can see the stars!")),
    }
}

fn check_skystate(state: &ThingsInTheSky) {
    match state {
        ThingsInTheSky::Sun(description) => println!("{}", description), // Give the string the name description so we can use it
        ThingsInTheSky::Stars(n) => println!("{}", n), // Or you can name it n. Or anything else - it doesn't matter
    }
}

fn main() {
    let time = 8; // it's 8 o'clock
    let skystate = create_skystate(time); // create_skystate returns a ThingsInTheSky
    check_skystate(&skystate); // Give it a reference so it can read the variable skystate
}

This prints the same thing: I can see the sun!

You can also "import" an enum so you don't have to type so much. Here's an example where we have to type Mood:: every time we match on our mood:

enum Mood {
    Happy,
    Sleepy,
    NotBad,
    Angry,
}

fn match_mood(mood: &Mood) -> i32 {
    let happiness_level = match mood {
        Mood::Happy => 10, // Here we type Mood:: every time
        Mood::Sleepy => 6,
        Mood::NotBad => 7,
        Mood::Angry => 2,
    };
    happiness_level
}

fn main() {
    let my_mood = Mood::NotBad;
    let happiness_level = match_mood(&my_mood);
    println!("Out of 1 to 10, my happiness is {}", happiness_level);
}

It prints Out of 1 to 10, my happiness is 7. Let's import so we can type less. To import everything, write *. Note: it's the same key as * for dereferencing but is completely different.

enum Mood {
    Happy,
    Sleepy,
    NotBad,
    Angry,
}

fn match_mood(mood: &Mood) -> i32 {
    use Mood::*; // We imported everything in Mood. Now we can just write Happy, Sleepy, etc.
    let happiness_level = match mood {
        Happy => 10, // We don't have to write Mood:: anymore
        Sleepy => 6,
        NotBad => 7,
        Angry => 2,
    };
    happiness_level
}

fn main() {
    let my_mood = Mood::Happy;
    let happiness_level = match_mood(&my_mood);
    println!("Out of 1 to 10, my happiness is {}", happiness_level);
}

Parts of an enum can also be turned into an integer. That's because Rust gives each arm of an enum a number that starts with 0 for its own use. You can do things with it if your enum doesn't have any other data in it.

enum Season {
    Spring, // If this was Spring(String) or something it wouldn't work
    Summer,
    Autumn,
    Winter,
}

fn main() {
    use Season::*;
    let four_seasons = vec![Spring, Summer, Autumn, Winter];
    for season in four_seasons {
        println!("{}", season as u32);
    }
}

This prints:

0
1
2
3

Though you can give it a different number, if you want - Rust doesn't care and can use it in the same way. Just add an = and your number to the variant that you want to have a number. You don't have to give all of them a number. But if you don't, Rust will just add 1 from the arm before to give it a number.

enum Star {
    BrownDwarf = 10,
    RedDwarf = 50,
    YellowStar = 100,
    RedGiant = 1000,
    DeadStar, // Think about this one. What number will it have?
}

fn main() {
    use Star::*;
    let starvec = vec![BrownDwarf, RedDwarf, YellowStar, RedGiant];
    for star in starvec {
        match star as u32 {
            size if size <= 80 => println!("Not the biggest star."), // Remember: size doesn't mean anything. It's just a name we chose so we can print it
            size if size >= 80 => println!("This is a good-sized star."),
            _ => println!("That star is pretty big!"),
        }
    }
    println!("What about DeadStar? It's the number {}.", DeadStar as u32);
}

This prints:

Not the biggest star.
Not the biggest star.
This is a good-sized star.
This is a good-sized star.
What about DeadStar? It's the number 1001.

DeadStar would have been number 4, but now it's 1001.

Enums to use multiple types

You know that items in a Vec, array, etc. all need the same type (only tuples are different). But you can actually use an enum to put different types in. Imagine we want to have a Vec with u32s or i32s. Of course, you can make a Vec<(u32, i32)> (a vec with (u32, i32) tuples) but we only want one each time. So here you can use an enum. Here is a simple example:

enum Number {
    U32(u32),
    I32(i32),
}

fn main() {}

So there are two variants: the U32 variant with a u32 inside, and the I32 variant with i32 inside. U32 and I32 are just names we made. They could have been UThirtyTwo or IThirtyTwo or anything else.

Now, if we put them into a Vec we just have a Vec<Number>, and the compiler is happy because it's all the same type. The compiler doesn't care that we have either u32 or i32 because they are all inside a single type called Number. And because it's an enum, you have to pick one, which is what we want. We will use the .is_positive() method to pick. If it's true then we will choose U32, and if it's false then we will choose I32.

Now the code looks like this:

enum Number {
    U32(u32),
    I32(i32),
}

fn get_number(input: i32) -> Number {
    let number = match input.is_positive() {
        true => Number::U32(input as u32), // change it to u32 if it's positive
        false => Number::I32(input), // otherwise just give the number because it's already i32
    };
    number
}


fn main() {
    let my_vec = vec![get_number(-800), get_number(8)];

    for item in my_vec {
        match item {
            Number::U32(number) => println!("It's a u32 with the value {}", number),
            Number::I32(number) => println!("It's an i32 with the value {}", number),
        }
    }
}

This prints what we wanted to see:

It's an i32 with the value -800
It's a u32 with the value 8

Loops

With loops you can tell Rust to continue something until you want it to stop. You use loop to start a loop that does not stop, unless you tell it when to break.

fn main() { // This program will never stop
    loop {

    }
}

So let's tell the compiler when it can break.

fn main() {
    let mut counter = 0; // set a counter to 0
    loop {
        counter +=1; // increase the counter by 1
        println!("The counter is now: {}", counter);
        if counter == 5 { // stop when counter == 5
            break;
        }
    }
}

This will print:

The counter is now: 1
The counter is now: 2
The counter is now: 3
The counter is now: 4
The counter is now: 5

If you have a loop inside of a loop, you can give them names. With names, you can tell Rust which loop to break out of. Use ' (called a "tick") and a : to give it a name:

fn main() {
    let mut counter = 0;
    let mut counter2 = 0;
    println!("Now entering the first loop.");

    'first_loop: loop {
        // Give the first loop a name
        counter += 1;
        println!("The counter is now: {}", counter);
        if counter > 9 {
            // Starts a second loop inside this loop
            println!("Now entering the second loop.");

            'second_loop: loop {
                // now we are inside 'second_loop
                println!("The second counter is now: {}", counter2);
                counter2 += 1;
                if counter2 == 3 {
                    break 'first_loop; // Break out of 'first_loop so we can exit the program
                }
            }
        }
    }
}

This will print:

Now entering the first loop.
The counter is now: 1
The counter is now: 2
The counter is now: 3
The counter is now: 4
The counter is now: 5
The counter is now: 6
The counter is now: 7
The counter is now: 8
The counter is now: 9
The counter is now: 10
Now entering the second loop.
The second counter is now: 0
The second counter is now: 1
The second counter is now: 2

A while loop is a loop that continues while something is still true. Each loop, Rust will check if it is still true. If it becomes false, Rust will stop the loop.

fn main() {
    let mut counter = 0;

    while counter < 5 {
        counter +=1;
        println!("The counter is now: {}", counter);
    }
}

A for loop lets you tell Rust what to do each time. But in a for loop, the loop stops after a certain number of times. for loops use ranges very often. You use .. and ..= to create a range.

  • .. creates an exclusive range: 0..3 creates 0, 1, 2.
  • ..= creates an inclusive range: 0..=3 = 0, 1, 2, 3.
fn main() {
    for number in 0..3 {
        println!("The number is: {}", number);
    }

    for number in 0..=3 {
        println!("The next number is: {}", number);
    }
}

This prints:

The number is: 0
The number is: 1
The number is: 2
The next number is: 0
The next number is: 1
The next number is: 2
The next number is: 3

Also notice that number becomes the variable name for 0..3. We could have called it n, or ntod_het___hno_f, or anything. We can then use that name in println!.

If you don't need a variable name, use _.

fn main() {
    for _ in 0..3 {
        println!("Printing the same thing three times");
    }
}

This prints:

Printing the same thing three times
Printing the same thing three times
Printing the same thing three times

because we didn't give it any number to print each time.

And actually, if you give a variable name and don't use it, Rust will tell you:

fn main() {
    for number in 0..3 {
        println!("Printing the same thing three times");
    }
}

This prints the same thing as above. The program compiles fine, but Rust will remind you that you didn't use number:

warning: unused variable: `number`
 --> src\main.rs:2:9
  |
2 |     for number in 0..3 {
  |         ^^^^^^ help: if this is intentional, prefix it with an underscore: `_number`

Rust suggests writing _number instead of _. Putting _ in front of a variable name means "maybe I will use it later". But using just _ means "I don't care about this variable at all". So you can put _ in front of variable names if you will use them later and don't want the compiler to tell you about them.

You can also use break to return a value. You write the value right after break and use a ;. Here is an example with a loop and a break that gives my_number its value.

fn main() {
    let mut counter = 5;
    let my_number = loop {
        counter +=1;
        if counter % 53 == 3 {
            break counter;
        }
    };
    println!("{}", my_number);
}

This prints 56. break counter; means "break and return the value of counter". And because the whole block starts with let, my_number gets the value.

Now that we know how to use loops, here is a better solution to our match problem with colours from before. It is a better solution because we want to compare everything, and a for loop looks at every item.

fn match_colours(rbg: (i32, i32, i32)) {
    println!("Comparing a colour with {} red, {} blue, and {} green:", rbg.0, rbg.1, rbg.2);
    let new_vec = vec![(rbg.0, "red"), (rbg.1, "blue"), (rbg.2, "green")]; // Put the colours in a vec. Inside are tuples with the colour names
    let mut all_have_at_least_10 = true; // Start with true. We will set it to false if one colour is less than 10
    for item in new_vec {
        if item.0 < 10 {
            all_have_at_least_10 = false; // Now it's false
            println!("Not much {}.", item.1) // And we print the colour name.
        }
    }
    if all_have_at_least_10 { // Check if it's still true, and print if true
        println!("Each colour has at least 10.")
    }
    println!(); // Add one more line
}

fn main() {
    let first = (200, 0, 0);
    let second = (50, 50, 50);
    let third = (200, 50, 0);

    match_colours(first);
    match_colours(second);
    match_colours(third);
}

This prints:

Comparing a colour with 200 red, 0 blue, and 0 green:
Not much blue.
Not much green.

Comparing a colour with 50 red, 50 blue, and 50 green:
Each colour has at least 10.

Comparing a colour with 200 red, 50 blue, and 0 green:
Not much green.

Implementing structs and enums

This is where you can start to give your structs and enums some real power. To call functions on a struct or an enum, use an impl block. These functions are called methods. There are two kinds of methods in an impl block.

  • Methods: these take self (or &self or &mut self). Regular methods use a . (a period). .clone() is an example of a regular method.
  • Associated functions (known as "static" methods in some languages): these do not take self. Associated means "related to". They are written differently, using ::. String::from() is an associated function, and so is Vec::new(). You see associated functions most often used to create new variables.

In our example we are going to create animals and print them.

For a new struct or enum, you need to give it Debug if you want to use {:?} to print, so we will do that. If you write #[derive(Debug)] above the struct or enum then you can print it with {:?}. These messages with #[] are called attributes. You can sometimes use them to tell the compiler to give your struct an ability like Debug. There are many attributes and we will learn about them later. But derive is probably the most common and you see it a lot above structs and enums.

#[derive(Debug)]
struct Animal {
    age: u8,
    animal_type: AnimalType,
}

#[derive(Debug)]
enum AnimalType {
    Cat,
    Dog,
}

impl Animal {
    fn new() -> Self {
        // Self means Animal.
        // You can also write Animal instead of Self

        Self {
            // When we write Animal::new(), we always get a cat that is 10 years old
            age: 10,
            animal_type: AnimalType::Cat,
        }
    }

    fn change_to_dog(&mut self) { // because we are inside Animal, &mut self means &mut Animal
                                  // use .change_to_dog() to change the cat to a dog
                                  // with &mut self we can change it
        println!("Changing animal to dog!");
        self.animal_type = AnimalType::Dog;
    }

    fn change_to_cat(&mut self) {
        // use .change_to_cat() to change the dog to a cat
        // with &mut self we can change it
        println!("Changing animal to cat!");
        self.animal_type = AnimalType::Cat;
    }

    fn check_type(&self) {
        // we want to read self
        match self.animal_type {
            AnimalType::Dog => println!("The animal is a dog"),
            AnimalType::Cat => println!("The animal is a cat"),
        }
    }
}



fn main() {
    let mut new_animal = Animal::new(); // Associated function to create a new animal
                                        // It is a cat, 10 years old
    new_animal.check_type();
    new_animal.change_to_dog();
    new_animal.check_type();
    new_animal.change_to_cat();
    new_animal.check_type();
}

This prints:

The animal is a cat
Changing animal to dog!
The animal is a dog
Changing animal to cat!
The animal is a cat

Remember that Self (the type Self) and self (the variable self) are abbreviations. (abbreviation = short way to write)

So in our code, Self = Animal. Also, fn change_to_dog(&mut self) means fn change_to_dog(&mut Animal).

Here is one more small example. This time we will use impl on an enum:

enum Mood {
    Good,
    Bad,
    Sleepy,
}

impl Mood {
    fn check(&self) {
        match self {
            Mood::Good => println!("Feeling good!"),
            Mood::Bad => println!("Eh, not feeling so good"),
            Mood::Sleepy => println!("Need sleep NOW"),
        }
    }
}

fn main() {
    let my_mood = Mood::Sleepy;
    my_mood.check();
}

This prints Need sleep NOW.

Destructuring

Let's look at some more destructuring. You can get the values from a struct or enum by using let backwards. We learned that this is destructuring, because you get variables that are not part of a structure. Now you have the values separately. First a simple example:

struct Person { // make a simple struct for a person
    name: String,
    real_name: String,
    height: u8,
    happiness: bool
}

fn main() {
    let papa_doc = Person { // create variable papa_doc
        name: "Papa Doc".to_string(),
        real_name: "Clarence".to_string(),
        height: 170,
        happiness: false
    };

    let Person { // destructure papa_doc
        name: a,
        real_name: b,
        height: c,
        happiness: d
    } = papa_doc;

    println!("They call him {} but his real name is {}. He is {} cm tall and is he happy? {}", a, b, c, d);
}

This prints: They call him Papa Doc but his real name is Clarence. He is 170 cm tall and is he happy? false

You can see that it's backwards. First we say let papa_doc = Person { fields } to create the struct. Then we say let Person { fields } = papa_doc to destructure it.

You don't have to write name: a - you can just write name. But here we write name: a because we want to use a variable with the name a.

Now a bigger example. In this example we have a City struct. We give it a new function to make it. Then we have a process_city_values function to do things with the values. In the function we just create a Vec, but you can imagine that we can do much more after we destructure it.

struct City {
    name: String,
    name_before: String,
    population: u32,
    date_founded: u32,
}

impl City {
    fn new(name: String, name_before: String, population: u32, date_founded: u32) -> Self {
        Self {
            name,
            name_before,
            population,
            date_founded,
        }
    }
}

fn process_city_values(city: &City) {
    let City {
        name,
        name_before,
        population,
        date_founded,
    } = city;
        // now we have the values to use separately
    let two_names = vec![name, name_before];
    println!("The city's two names are {:?}", two_names);
}

fn main() {
    let tallinn = City::new("Tallinn".to_string(), "Reval".to_string(), 426_538, 1219);
    process_city_values(&tallinn);
}

This prints The city's two names are ["Tallinn", "Reval"].

References and the dot operator

We learned that when you have a reference, you need to use * to get to the value. A reference is a different type, so this won't work:

fn main() {
    let my_number = 9;
    let reference = &my_number;

    println!("{}", my_number == reference); // ⚠️
}

The compiler prints:

error[E0277]: can't compare `{integer}` with `&{integer}`
 --> src\main.rs:5:30
  |
5 |     println!("{}", my_number == reference);
  |                              ^^ no implementation for `{integer} == &{integer}`

So we change line 5 to println!("{}", my_number == *reference); and now it prints true because it's now i32 == i32, not i32 == &i32. This is called dereferencing.

But when you use a method, Rust will dereference for you. The . in a method is called the dot operator, and it does dereferencing for free.

First, let's make a struct with one u8 field. Then we will make a reference to it and try to compare. It will not work:

struct Item {
    number: u8,
}

fn main() {
    let item = Item {
        number: 8,
    };

    let reference_number = &item.number; // reference number type is &u8

    println!("{}", reference_number == 8); // ⚠️ &u8 and u8 cannot be compared
}

To make it work, we need to dereference: println!("{}", *reference_number == 8);.

But with the dot operator, we don't need *. For example:

struct Item {
    number: u8,
}

fn main() {
    let item = Item {
        number: 8,
    };

    let reference_item = &item;

    println!("{}", reference_item.number == 8); // we don't need to write *reference_item.number
}

Now let's create a method for Item that compares number to another number. We don't need to use * anywhere:

struct Item {
    number: u8,
}

impl Item {
    fn compare_number(&self, other_number: u8) { // takes a reference to self
        println!("Are {} and {} equal? {}", self.number, other_number, self.number == other_number);
            // We don't need to write *self.number
    }
}

fn main() {
    let item = Item {
        number: 8,
    };

    let reference_item = &item; // This is type &Item
    let reference_item_two = &reference_item; // This is type &&Item

    item.compare_number(8); // the method works
    reference_item.compare_number(8); // it works here too
    reference_item_two.compare_number(8); // and here

}

So just remember: when you use the . operator, you don't need to worry about *.

Generics

In functions, you write what type to take as input:

fn return_number(number: i32) -> i32 {
    println!("Here is your number.");
    number
}

fn main() {
    let number = return_number(5);
}

But what if you want to take more than just i32? You can use generics for this. Generics means "maybe one type, maybe another type".

For generics, you use angle brackets with the type inside, like this: <T> This means "any type you put into the function". Usually, generics uses types with one capital letter (T, U, V, etc.), though you don't have to just use one letter.

This is how you change the function to make it generic:

fn return_number<T>(number: T) -> T {
    println!("Here is your number.");
    number
}

fn main() {
    let number = return_number(5);
}

The important part is the <T> after the function name. Without this, Rust will think that T is a concrete (concrete = not generic) type, like String or i8.

This is easier to understand if we write out a type name. See what happens when we change T to MyType:

fn return_number(number: MyType) -> MyType { // ⚠️
    println!("Here is your number.");
    number
}

As you can see, MyType is concrete, not generic. So we need to write this and so now it works:

fn return_number<MyType>(number: MyType) -> MyType {
    println!("Here is your number.");
    number
}

fn main() {
    let number = return_number(5);
}

So the single letter T is for human eyes, but the part after the function name is for the compiler's "eyes". Without it, it's not generic.

Now we will go back to type T, because Rust code usually uses T.

You will remember that some types in Rust are Copy, some are Clone, some are Display, some are Debug, and so on. With Debug, we can print with {:?}. So now you can see that we have a problem if we want to print T:

fn print_number<T>(number: T) {
    println!("Here is your number: {:?}", number); // ⚠️
}

fn main() {
    print_number(5);
}

print_number needs Debug to print number, but is T a type with Debug? Maybe not. Maybe it doesn't have #[derive(Debug)], who knows. The compiler doesn't know either, so it gives an error:

error[E0277]: `T` doesn't implement `std::fmt::Debug`
  --> src\main.rs:29:43
   |
29 |     println!("Here is your number: {:?}", number);
   |                                           ^^^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`

T doesn't implement Debug. So do we implement Debug for T? No, because we don't know what T is. But we can tell the function: "Don't worry, because any type T for this function will have Debug".

use std::fmt::Debug; // Debug is located at std::fmt::Debug. So now we can just write 'Debug'.

fn print_number<T: Debug>(number: T) { // <T: Debug> is the important part
    println!("Here is your number: {:?}", number);
}

fn main() {
    print_number(5);
}

So now the compiler knows: "Okay, this type T is going to have Debug". Now the code works, because i32 has Debug. Now we can give it many types: String, &str, and so on, because they all have Debug.

Now we can create a struct and give it Debug with #[derive(Debug)], so now we can print it too. Our function can take i32, the struct Animal, and more:

use std::fmt::Debug;

#[derive(Debug)]
struct Animal {
    name: String,
    age: u8,
}

fn print_item<T: Debug>(item: T) {
    println!("Here is your item: {:?}", item);
}

fn main() {
    let charlie = Animal {
        name: "Charlie".to_string(),
        age: 1,
    };

    let number = 55;

    print_item(charlie);
    print_item(number);
}

This prints:

Here is your item: Animal { name: "Charlie", age: 1 }
Here is your item: 55

Sometimes we need more than one type in a generic function. We have to write out each type name, and think about how we want to use it. In this example, we want two types. First we want to print a statement for type T. Printing with {} is nicer, so we will require Display for T.

Next is type U, and the two variables num_1 and num_2 have type U (U is some sort of number). We want to compare them, so we need PartialOrd. That trait lets us use things like <, >, ==, and so on. We want to print them too, so we require Display for U as well.

use std::fmt::Display;
use std::cmp::PartialOrd;

fn compare_and_display<T: Display, U: Display + PartialOrd>(statement: T, num_1: U, num_2: U) {
    println!("{}! Is {} greater than {}? {}", statement, num_1, num_2, num_1 > num_2);
}

fn main() {
    compare_and_display("Listen up!", 9, 8);
}

This prints Listen up!! Is 9 greater than 8? true.

So fn compare_and_display<T: Display, U: Display + PartialOrd>(statement: T, num_1: U, num_2: U) says:

  • The function name is compare_and_display,
  • The first type is T, and it is generic. It must be a type that can print with {}.
  • The next type is U, and it is generic. It must be a type that can print with {}. Also, it must be a type that can compare (use >, <, and ==).

Now we can give compare_and_display different types. statement can be a String, a &str, anything with Display.

To make generic functions easier to read, we can also write it like this with where right before the code block:

use std::cmp::PartialOrd;
use std::fmt::Display;

fn compare_and_display<T, U>(statement: T, num_1: U, num_2: U)
where
    T: Display,
    U: Display + PartialOrd,
{
    println!("{}! Is {} greater than {}? {}", statement, num_1, num_2, num_1 > num_2);
}

fn main() {
    compare_and_display("Listen up!", 9, 8);
}

Using where is a good idea when you have many generic types.

Also note:

  • If you have one type T and another type T, they must be the same.
  • If you have one type T and another type U, they can be different. But they can also be the same.

For example:

use std::fmt::Display;

fn say_two<T: Display, U: Display>(statement_1: T, statement_2: U) { // Type T needs Display, type U needs Display
    println!("I have two things to say: {} and {}", statement_1, statement_2);
}

fn main() {

    say_two("Hello there!", String::from("I hate sand.")); // Type T is a &str, but type U is a String.
    say_two(String::from("Where is Padme?"), String::from("Is she all right?")); // Both types are String.
}

This prints:

I have two things to say: Hello there! and I hate sand.
I have two things to say: Where is Padme? and Is she all right?

Option and Result

We understand enums and generics now, so we can understand Option and Result. Rust uses these two enums to make code safer.

We will start with Option.

Option

You use Option when you have a value that might exist, or might not exist. When a value exists it is Some(value) and when it doesn't it's just None, Here is an example of bad code that can be improved with Option.

    // ⚠️
fn take_fifth(value: Vec<i32>) -> i32 {
    value[4]
}

fn main() {
    let new_vec = vec![1, 2];
    let index = take_fifth(new_vec);
}

When we run the code, it panics. Here is the message:

thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 4', src\main.rs:34:5

Panic means that the program stops before the problem happens. Rust sees that the function wants something impossible, and stops. It "unwinds the stack" (takes the values off the stack) and tells you "sorry, I can't do that".

So now we will change the return type from i32 to Option<i32>. This means "give me a Some(i32) if it's there, and give me None if it's not". We say that the i32 is "wrapped" in an Option, which means that it's inside an Option. You have to do something to get the value out.

fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 { // .len() gives the length of the vec.
                         // It must be at least 5.
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    println!("{:?}, {:?}", take_fifth(new_vec), take_fifth(bigger_vec));
}

This prints None, Some(5). This is good, because now we don't panic anymore. But how do we get the value 5?

We can get the value inside an option with .unwrap(), but be careful with .unwrap(). It's just like unwrapping a present: maybe there's something good inside, or maybe there's an angry snake inside. You only want to .unwrap() if you are sure. If you unwrap a value that is None, the program will panic.

// ⚠️
fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 {
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    println!("{:?}, {:?}",
        take_fifth(new_vec).unwrap(), // this one is None. .unwrap() will panic!
        take_fifth(bigger_vec).unwrap()
    );
}

The message is:

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src\main.rs:14:9

But we don't have to use .unwrap(). We can use a match. Then we can print the value we have Some, and not touch it if we have None. For example:

fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 {
        None
    } else {
        Some(value[4])
    }
}

fn handle_option(my_option: Vec<Option<i32>>) {
  for item in my_option {
    match item {
      Some(number) => println!("Found a {}!", number),
      None => println!("Found a None!"),
    }
  }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    let mut option_vec = Vec::new(); // Make a new vec to hold our options
                                     // The vec is type: Vec<Option<i32>>. That means a vec of Option<i32>.

    option_vec.push(take_fifth(new_vec)); // This pushes "None" into the vec
    option_vec.push(take_fifth(bigger_vec)); // This pushes "Some(5)" into the vec

    handle_option(option_vec); // handle_option looks at every option in the vec.
                               // It prints the value if it is Some. It doesn't touch it if it is None.
}

This prints:

Found a None!
Found a 5!

Because we know generics, we are able to read the code for Option. It looks like this:

enum Option<T> {
    None,
    Some(T),
}

fn main() {}

The important point to remember: with Some, you have a value of type T (any type). Also note that the angle brackets after the enum name around T is what tells the compiler that it's generic. It has no trait like Display or anything to limit it, so it can be anything. But with None, you don't have anything.

So in a match statement for Option you can't say:

// 🚧
Some(value) => println!("The value is {}", value),
None(value) => println!("The value is {}", value),

because None is just None.

Of course, there are easier ways to use Option. In this code, we will use a method called .is_some() to tell us if it is Some. (Yes, there is also a method called .is_none().) In this easier way, we don't need handle_option() anymore. We also don't need a vec for the Options.

fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 {
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    let vec_of_vecs = vec![new_vec, bigger_vec];
    for vec in vec_of_vecs {
        let inside_number = take_fifth(vec);
        if inside_number.is_some() {
            // .is_some() returns true if we get Some, false if we get None
            println!("We got: {}", inside_number.unwrap()); // now it is safe to use .unwrap() because we already checked
        } else {
            println!("We got nothing.");
        }
    }
}

This prints:

We got nothing.
We got: 5

Result

Result is similar to Option, but here is the difference:

  • Option is about Some or None (value or no value),
  • Result is about Ok or Err (okay result, or error result).

So Option is if you are thinking: "Maybe there will be something, and maybe there won't." But Result is if you are thinking: "Maybe it will fail."

To compare, here are the signatures for Option and Result.

enum Option<T> {
    None,
    Some(T),
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn main() {}

So Result has a value inside of Ok, and a value inside of Err. That is because errors usually contain information that describes the error.

Result<T, E> means you need to think of what you want to return for Ok, and what you want to return for Err. Actually, you can decide anything. Even this is okay:

fn check_error() -> Result<(), ()> {
    Ok(())
}

fn main() {
    check_error();
}

check_error says "return () if we get Ok, and return () if we get Err". Then we return Ok with a ().

The compiler gives us an interesting warning:

warning: unused `std::result::Result` that must be used
 --> src\main.rs:6:5
  |
6 |     check_error();
  |     ^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: this `Result` may be an `Err` variant, which should be handled

This is true: we only returned the Result but it could have been an Err. So let's handle the error a bit, even though we're still not really doing anything.

fn give_result(input: i32) -> Result<(), ()> {
    if input % 2 == 0 {
        return Ok(())
    } else {
        return Err(())
    }
}

fn main() {
    if give_result(5).is_ok() {
        println!("It's okay, guys")
    } else {
        println!("It's an error, guys")
    }
}

This prints It's an error, guys. So we just handled our first error.

Remember, the four methods to easily check are .is_some(), is_none(), is_ok(), and is_err().

Sometimes a function with Result will use a String for the Err value. This is not the best method to use, but it is a little better than what we've done so far.

fn check_if_five(number: i32) -> Result<i32, String> {
    match number {
        5 => Ok(number),
        _ => Err("Sorry, the number wasn't five.".to_string()), // This is our error message
    }
}

fn main() {
    let mut result_vec = Vec::new(); // Create a new vec for the results

    for number in 2..7 {
        result_vec.push(check_if_five(number)); // push each result into the vec
    }

    println!("{:?}", result_vec);
}

Our vec prints:

[Err("Sorry, the number wasn\'t five."), Err("Sorry, the number wasn\'t five."), Err("Sorry, the number wasn\'t five."), Ok(5),
Err("Sorry, the number wasn\'t five.")]

Just like Option, .unwrap() on Err will panic.

    // ⚠️
fn main() {
    let error_value: Result<i32, &str> = Err("There was an error"); // Create a Result that is already an Err
    println!("{}", error_value.unwrap()); // Unwrap it
}

The program panics, and prints:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "There was an error"', src\main.rs:30:20

This information helps you fix your code. src\main.rs:30:20 means "inside main.rs in directory src, on line 30 and column 20". So you can go there to look at your code and fix the problem.

You can also create your own error types. Result functions in the standard library and other people's code usually do this. For example, this function from the standard library:

// 🚧
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>

This function takes a vector of bytes (u8) and tries to make a String. So the success case for the Result is a String and the error case is FromUtf8Error. You can give your error type any name you want.

Using a match with Option and Result sometimes requires a lot of code. For example, the .get() method returns an Option on a Vec.

fn main() {
    let my_vec = vec![2, 3, 4];
    let get_one = my_vec.get(0); // 0 to get the first number
    let get_two = my_vec.get(10); // Returns None
    println!("{:?}", get_one);
    println!("{:?}", get_two);
}

This prints

Some(2)
None

So now we can match to get the values. Let's use a range from 0 to 10 to see if it matches the numbers in my_vec.

fn main() {
    let my_vec = vec![2, 3, 4];

    for index in 0..10 {
      match my_vec.get(index) {
        Some(number) => println!("The number is: {}", number),
        None => {}
      }
    }
}

This is good, but we don't do anything for None because we don't care. Here we can make the code smaller by using if let. if let means "do something if it matches, and don't do anything if it doesn't". if let is when you don't care about matching for everything.

fn main() {
    let my_vec = vec![2, 3, 4];

    for index in 0..10 {
      if let Some(number) = my_vec.get(index) {
        println!("The number is: {}", number);
      }
    }
}

Important to remember: if let Some(number) = my_vec.get(index) means "if you get Some(number) from my_vec.get(index)".

Also note: it uses one =. It is not a boolean.

while let is like a while loop for if let. Imagine that we have weather station data like this:

["Berlin", "cloudy", "5", "-7", "78"]
["Athens", "sunny", "not humid", "20", "10", "50"]

We want to get the numbers, but not the words. For the numbers, we can use a method called parse::<i32>(). parse() is the method, and ::<i32> is the type. It will try to turn the &str into an i32, and give it to us if it can. It returns a Result, because it might not work (like if you wanted it to parse "Billybrobby" - that's not a number).

We will also use .pop(). This takes the last item off of the vector.

fn main() {
    let weather_vec = vec![
        vec!["Berlin", "cloudy", "5", "-7", "78"],
        vec!["Athens", "sunny", "not humid", "20", "10", "50"],
    ];
    for mut city in weather_vec {
        println!("For the city of {}:", city[0]); // In our data, every first item is the city name
        while let Some(information) = city.pop() {
            // This means: keep going until you can't pop anymore
            // When the vector reaches 0 items, it will return None
            // and it will stop.
            if let Ok(number) = information.parse::<i32>() {
                // Try to parse the variable we called information
                // This returns a result. If it's Ok(number), it will print it
                println!("The number is: {}", number);
            }  // We don't write anything here because we do nothing if we get an error. Throw them all away
        }
    }
}

This will print:

For the city of Berlin:
The number is: 78
The number is: -7
The number is: 5
For the city of Athens:
The number is: 50
The number is: 10
The number is: 20

Other collections

Rust has many more types of collections. You can see them at https://doc.rust-lang.org/beta/std/collections/ in the standard library. That page has good explanations for why to use one type, so go there if you don't know what type you want. These collections are all inside std::collections in the standard library. The best way to use them is with a use statement, like we did with our enums. We will start with HashMap, which is very common.

HashMap (and BTreeMap)

A HashMap is a collection made out of keys and values. You use the key to look up the value that matches the key. You can create a new HashMap with just HashMap::new() and use .insert(key, value) to insert items.

A HashMap is not in order, so if you print every key in a HashMap together it will probably print differently. We can see this in an example:

use std::collections::HashMap; // This is so we can just write HashMap instead of std::collections::HashMap every time

struct City {
    name: String,
    population: HashMap<u32, u32>, // This will have the year and the population for the year
}

fn main() {

    let mut tallinn = City {
        name: "Tallinn".to_string(),
        population: HashMap::new(), // So far the HashMap is empty
    };

    tallinn.population.insert(1372, 3_250); // insert three dates
    tallinn.population.insert(1851, 24_000);
    tallinn.population.insert(2020, 437_619);


    for (year, population) in tallinn.population { // The HashMap is HashMap<u32, u32> so it returns a two items each time
        println!("In the year {} the city of {} had a population of {}.", year, tallinn.name, population);
    }
}

This prints:

In the year 1372 the city of Tallinn had a population of 3250.
In the year 2020 the city of Tallinn had a population of 437619.
In the year 1851 the city of Tallinn had a population of 24000.

or it might print:

In the year 1851 the city of Tallinn had a population of 24000.
In the year 2020 the city of Tallinn had a population of 437619.
In the year 1372 the city of Tallinn had a population of 3250.

You can see that it's not in order.

If you want a HashMap that you can sort, you can use a BTreeMap. Actually they are very similar to each other, so we can quickly change our HashMap to a BTreeMap to see. You can see that it is almost the same code.

use std::collections::BTreeMap; // Just change HashMap to BTreeMap

struct City {
    name: String,
    population: BTreeMap<u32, u32>, // Just change HashMap to BTreeMap
}

fn main() {

    let mut tallinn = City {
        name: "Tallinn".to_string(),
        population: BTreeMap::new(), // Just change HashMap to BTreeMap
    };

    tallinn.population.insert(1372, 3_250);
    tallinn.population.insert(1851, 24_000);
    tallinn.population.insert(2020, 437_619);

    for (year, population) in tallinn.population {
        println!("In the year {} the city of {} had a population of {}.", year, tallinn.name, population);
    }
}

Now it will always print:

In the year 1372 the city of Tallinn had a population of 3250.
In the year 1851 the city of Tallinn had a population of 24000.
In the year 2020 the city of Tallinn had a population of 437619.

Now we will go back to HashMap.

You can get a value in a HashMap by just putting the key in [] square brackets. In this next example we will bring up the value for the key Bielefeld, which is Germany. But be careful, because the program will crash if there is no key. If you write println!("{:?}", city_hashmap["Bielefeldd"]); for example then it will crash, because Bielefeldd doesn't exist.

If you are not sure that there will be a key, you can use .get() which returns an Option. If it exists it will be Some(value), and if not you will get None instead of crashing the program. That's why .get() is the safer way to get a value from a HashMap.

use std::collections::HashMap;

fn main() {
    let canadian_cities = vec!["Calgary", "Vancouver", "Gimli"];
    let german_cities = vec!["Karlsruhe", "Bad Doberan", "Bielefeld"];

    let mut city_hashmap = HashMap::new();

    for city in canadian_cities {
        city_hashmap.insert(city, "Canada");
    }
    for city in german_cities {
        city_hashmap.insert(city, "Germany");
    }

    println!("{:?}", city_hashmap["Bielefeld"]);
    println!("{:?}", city_hashmap.get("Bielefeld"));
    println!("{:?}", city_hashmap.get("Bielefeldd"));
}

This prints:

"Germany"
Some("Germany")
None

This is because Bielefeld exists, but Bielefeldd does not exist.

If a HashMap already has a key when you try to put it in, it will overwrite its value:

use std::collections::HashMap;

fn main() {
    let mut book_hashmap = HashMap::new();

    book_hashmap.insert(1, "L'Allemagne Moderne");
    book_hashmap.insert(1, "Le Petit Prince");
    book_hashmap.insert(1, "섀도우 오브 유어 스마일");
    book_hashmap.insert(1, "Eye of the World");

    println!("{:?}", book_hashmap.get(&1));
}

This prints Some("Eye of the World"), because it was the last one you used .insert() for.

It is easy to check if an entry exists, because you can check with .get() which gives an Option:

use std::collections::HashMap;

fn main() {
    let mut book_hashmap = HashMap::new();

    book_hashmap.insert(1, "L'Allemagne Moderne");

    if book_hashmap.get(&1).is_none() { // is_none() returns a bool: true if it's None, false if it's Some
        book_hashmap.insert(1, "Le Petit Prince");
    }

    println!("{:?}", book_hashmap.get(&1));
}

This prints Some("L\'Allemagne Moderne") because there was already a key for 1, so we didn't insert Le Petit Prince.

HashMap has a very interesting method called .entry() that you definitely want to try out. With it you can try to make an entry and use another method like .or_insert() to insert the value if there is no key. The interesting part is that it also gives a mutable reference so you can change it if you want. First is an example where we just insert true every time we insert a book title into the HashMap.

Let's pretend that we have a library and want to keep track of our books.

use std::collections::HashMap;

fn main() {
    let book_collection = vec!["L'Allemagne Moderne", "Le Petit Prince", "Eye of the World", "Eye of the World"]; // Eye of the World appears twice

    let mut book_hashmap = HashMap::new();

    for book in book_collection {
        book_hashmap.entry(book).or_insert(true);
    }
    for (book, true_or_false) in book_hashmap {
        println!("Do we have {}? {}", book, true_or_false);
    }
}

This prints:

Do we have Eye of the World? true
Do we have Le Petit Prince? true
Do we have L'Allemagne Moderne? true

But that's not exactly what we want. Maybe it would be better to count the number of books so that we know that there are two copies of Eye of the World. First let's look at what .entry() does, and what .or_insert() does. .entry() actually returns an enum called Entry:

pub fn entry(&mut self, key: K) -> Entry<K, V> // 🚧

Here is the page for Entry. Here is a simple version of its code. K means key and V means value.

// 🚧
use std::collections::hash_map::*;

enum Entry<K, V> {
    Occupied(OccupiedEntry<K, V>),
    Vacant(VacantEntry<K, V>),
}

Then when we call .or_insert(), it looks at the enum and decides what to do.

fn or_insert(self, default: V) -> &mut V { // 🚧
    match self {
        Occupied(entry) => entry.into_mut(),
        Vacant(entry) => entry.insert(default),
    }
}

The interesting part is that it returns a mut reference: &mut V. That means you can use let to attach it to a variable, and change the variable to change the value in the HashMap. So for every book we will insert a 0 if there is no entry. And if there is one, we will use += 1 on the reference to increase the number. Now it looks like this:

use std::collections::HashMap;

fn main() {
    let book_collection = vec!["L'Allemagne Moderne", "Le Petit Prince", "Eye of the World", "Eye of the World"];

    let mut book_hashmap = HashMap::new();

    for book in book_collection {
        let return_value = book_hashmap.entry(book).or_insert(0); // return_value is a mutable reference. If nothing is there, it will be 0
        *return_value +=1; // Now return_value is at least 1. And if there was another book, it will go up by 1
    }

    for (book, number) in book_hashmap {
        println!("{}, {}", book, number);
    }
}

The important part is let return_value = book_hashmap.entry(book).or_insert(0);. If you take out the let, you get book_hashmap.entry(book).or_insert(0). Without let it does nothing: it inserts 0, and nobody takes the mutable reference to 0. So we bind it to return_value so we can keep the 0. Then we increase the value by 1, which gives at least 1 for every book in the HashMap. Then when .entry() looks at Eye of the World again it doesn't insert anything, but it gives us a mutable 1. Then we increase it to 2, and that's why it prints this:

L'Allemagne Moderne, 1
Le Petit Prince, 1
Eye of the World, 2

You can also do things with .or_insert() like insert a vec and then push into the vec. Let's pretend that we asked men and women on the street what they think of a politician. They give a rating from 0 to 10. Then we want to put the numbers together to see if the politician is more popular with men or women. It can look like this:

use std::collections::HashMap;

fn main() {
    let data = vec![ // This is the raw data
        ("male", 9),
        ("female", 5),
        ("male", 0),
        ("female", 6),
        ("female", 5),
        ("male", 10),
    ];

    let mut survey_hash = HashMap::new();

    for item in data { // This gives a tuple of (&str, i32)
        survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1); // This pushes the number into the Vec inside
    }

    for (male_or_female, numbers) in survey_hash {
        println!("{:?}: {:?}", male_or_female, numbers);
    }
}

This prints:

"female", [5, 6, 5]
"male", [9, 0, 10]

The important line is: survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1); So if it sees "female" it will check to see if there is "female" already in the HashMap. If not, it will insert a Vec::new(), then push the number in. If it sees "female" already in the HashMap, it will not insert a new Vec, and will just push the number into it.

HashSet and BTreeSet

A HashSet is actually a HashMap that only has keys. On the page for HashSet it explains this on the top:

A hash set implemented as a HashMap where the value is (). So it's a HashMap with keys, no values.

You often use a HashSet if you just want to know if a key exists, or doesn't exist.

Imagine that you have 100 random numbers, and each number between 1 and 100. If you do this, some numbers will appear more than once, while some won't appear at all. If you put them into a HashSet then you will have a list of all the numbers that appeared.

use std::collections::HashSet;

fn main() {
    let many_numbers = vec![
        94, 42, 59, 64, 32, 22, 38, 5, 59, 49, 15, 89, 74, 29, 14, 68, 82, 80, 56, 41, 36, 81, 66,
        51, 58, 34, 59, 44, 19, 93, 28, 33, 18, 46, 61, 76, 14, 87, 84, 73, 71, 29, 94, 10, 35, 20,
        35, 80, 8, 43, 79, 25, 60, 26, 11, 37, 94, 32, 90, 51, 11, 28, 76, 16, 63, 95, 13, 60, 59,
        96, 95, 55, 92, 28, 3, 17, 91, 36, 20, 24, 0, 86, 82, 58, 93, 68, 54, 80, 56, 22, 67, 82,
        58, 64, 80, 16, 61, 57, 14, 11];

    let mut number_hashset = HashSet::new();

    for number in many_numbers {
        number_hashset.insert(number);
    }

    let hashset_length = number_hashset.len(); // The length tells us how many numbers are in it
    println!("There are {} unique numbers, so we are missing {}.", hashset_length, 100 - hashset_length);

    // Let's see what numbers we are missing
    let mut missing_vec = vec![];
    for number in 0..100 {
        if number_hashset.get(&number).is_none() { // If .get() returns None,
            missing_vec.push(number);
        }
    }

    print!("It does not contain: ");
    for number in missing_vec {
        print!("{} ", number);
    }
}

This prints:

There are 66 unique numbers, so we are missing 34.
It does not contain: 1 2 4 6 7 9 12 21 23 27 30 31 39 40 45 47 48 50 52 53 62 65 69 70 72 75 77 78 83 85 88 97 98 99

A BTreeSet is similar to a HashSet in the same way that a BTreeMap is similar to a HashMap. If we print each item in the HashSet, we don't know what the order will be:

for entry in number_hashset { // 🚧
    print!("{} ", entry);
}

Maybe it will print this: 67 28 42 25 95 59 87 11 5 81 64 34 8 15 13 86 10 89 63 93 49 41 46 57 60 29 17 22 74 43 32 38 36 76 71 18 14 84 61 16 35 90 56 54 91 19 94 44 3 0 68 80 51 92 24 20 82 26 58 33 55 96 37 66 79 73. But it will almost never print it in the same way again.

Here as well, it is easy to change your HashSet to a BTreeSet if you decide you need ordering. In our code, we only need to make two changes to switch from a HashSet to a BTreeSet.

use std::collections::BTreeSet; // Change HashSet to BTreeSet

fn main() {
    let many_numbers = vec![
        94, 42, 59, 64, 32, 22, 38, 5, 59, 49, 15, 89, 74, 29, 14, 68, 82, 80, 56, 41, 36, 81, 66,
        51, 58, 34, 59, 44, 19, 93, 28, 33, 18, 46, 61, 76, 14, 87, 84, 73, 71, 29, 94, 10, 35, 20,
        35, 80, 8, 43, 79, 25, 60, 26, 11, 37, 94, 32, 90, 51, 11, 28, 76, 16, 63, 95, 13, 60, 59,
        96, 95, 55, 92, 28, 3, 17, 91, 36, 20, 24, 0, 86, 82, 58, 93, 68, 54, 80, 56, 22, 67, 82,
        58, 64, 80, 16, 61, 57, 14, 11];

    let mut number_btreeset = BTreeSet::new(); // Change HashSet to BTreeSet

    for number in many_numbers {
        number_btreeset.insert(number);
    }
    for entry in number_btreeset {
        print!("{} ", entry);
    }
}

Now it will print in order: 0 3 5 8 10 11 13 14 15 16 17 18 19 20 22 24 25 26 28 29 32 33 34 35 36 37 38 41 42 43 44 46 49 51 54 55 56 57 58 59 60 61 63 64 66 67 68 71 73 74 76 79 80 81 82 84 86 87 89 90 91 92 93 94 95 96.

BinaryHeap

A BinaryHeap is an interesting collection type, because it is mostly unordered but has a bit of order. It keeps the largest item in the front, but the other items are in any order.

We will use another list of items for an example, but this time smaller.

use std::collections::BinaryHeap;

fn show_remainder(input: &BinaryHeap<i32>) -> Vec<i32> { // This function shows the remainder in the BinaryHeap. Actually an iterator would be
                                                         // faster than a function - we will learn them later.
    let mut remainder_vec = vec![];
    for number in input {
        remainder_vec.push(*number)
    }
    remainder_vec
}

fn main() {
    let many_numbers = vec![0, 5, 10, 15, 20, 25, 30]; // These numbers are in order

    let mut my_heap = BinaryHeap::new();

    for number in many_numbers {
        my_heap.push(number);
    }

    while let Some(number) = my_heap.pop() { // .pop() returns Some(number) if a number is there, None if not. It pops from the front
        println!("Popped off {}. Remaining numbers are: {:?}", number, show_remainder(&my_heap));
    }
}

This prints:

Popped off 30. Remaining numbers are: [25, 15, 20, 0, 10, 5]
Popped off 25. Remaining numbers are: [20, 15, 5, 0, 10]
Popped off 20. Remaining numbers are: [15, 10, 5, 0]
Popped off 15. Remaining numbers are: [10, 0, 5]
Popped off 10. Remaining numbers are: [5, 0]
Popped off 5. Remaining numbers are: [0]
Popped off 0. Remaining numbers are: []

You can see that the number in the 0 index is always largest: 25, 20, 15, 10, 5, then 0. But the other ones are all different.

A good way to use a BinaryHeap is for a collection of things to do. Here we create a BinaryHeap<(u8, &str)> where the u8 is a number for the importance of the task. The &str is a description of what to do.

use std::collections::BinaryHeap;

fn main() {
    let mut jobs = BinaryHeap::new();

    // Add jobs to do throughout the day
    jobs.push((100, "Write back to email from the CEO"));
    jobs.push((80, "Finish the report today"));
    jobs.push((5, "Watch some YouTube"));
    jobs.push((70, "Tell your team members thanks for always working hard"));
    jobs.push((30, "Plan who to hire next for the team"));

    while let Some(job) = jobs.pop() {
        println!("You need to: {}", job.1);
    }
}

This will always print:

You need to: Write back to email from the CEO
You need to: Finish the report today
You need to: Tell your team members thanks for always working hard
You need to: Plan who to hire next for the team
You need to: Watch some YouTube

VecDeque

A VecDeque is a Vec that is good at popping items both off the front and the back. Rust has VecDeque because a Vec is great for popping off the back (the last item), but not so great off the front. When you use .pop() on a Vec, it just takes off the last item on the right and nothing else is moved. But if you take it off another part, all the items to the right are moved over one position to the left. You can see this in the description for .remove():

Removes and returns the element at position index within the vector, shifting all elements after it to the left.

So if you do this:

fn main() {
    let mut my_vec = vec![9, 8, 7, 6, 5];
    my_vec.remove(0);
}

it will remove 9. 8 in index 1 will move to index 0, 7 in index 2 will move to index 1, and so on. Imagine a big parking lot where every time one car leaves all the cars on the right side have to move over.

This, for example, is a lot of work for the computer. In fact, if you run it on the Playground it will probably just give up because it's too much work.

fn main() {
    let mut my_vec = vec![0; 600_000];
    for i in 0..600000 {
        my_vec.remove(0);
    }
}

This is a Vec of 600,000 zeros. Every time you use remove(0) on it, it moves each zero left one space to the left. And then it does it 600,000 times.

You don't have to worry about that with a VecDeque. It is usually a bit slower than a Vec, but if you have to do things on both ends then it is much faster. You can just use VecDeque::from with a Vec to make one. Our code above then looks like this:

use std::collections::VecDeque;

fn main() {
    let mut my_vec = VecDeque::from(vec![0; 600000]);
    for i in 0..600000 {
        my_vec.pop_front(); // pop_front is like .pop but for the front
    }
}

It is now much faster, and on the Playground it finishes in under a second instead of giving up.

In this next example we have a Vec of things to do. Then we make a VecDeque and use .push_front() to put them at the front, so the first item we added will be on the right. But each item we push is a (&str, bool): &str is the description and false means it's not done yet. We use our done() function to pop an item off the back, but we don't want to delete it. Instead, we change false to true and push it at the front so that we can keep it.

It looks like this:

use std::collections::VecDeque;

fn check_remaining(input: &VecDeque<(&str, bool)>) { // Each item is a (&str, bool)
    for item in input {
        if item.1 == false {
            println!("You must: {}", item.0);
        }
    }
}

fn done(input: &mut VecDeque<(&str, bool)>) {
    let mut task_done = input.pop_back().unwrap(); // pop off the back
    task_done.1 = true;                            // now it's done - mark as true
    input.push_front(task_done);                   // put it at the front now
}

fn main() {
    let mut my_vecdeque = VecDeque::new();
    let things_to_do = vec!["send email to customer", "add new product to list", "phone Loki back"];

    for thing in things_to_do {
        my_vecdeque.push_front((thing, false));
    }

    done(&mut my_vecdeque);
    done(&mut my_vecdeque);

    check_remaining(&my_vecdeque);

    for task in my_vecdeque {
        print!("{:?} ", task);
    }
}

This prints:

You must: phone Loki back
("add new product to list", true) ("send email to customer", true) ("phone Loki back", false)

The ? operator

There is an even shorter way to deal with Result (and Option), shorter than match and even shorter than if let. It is called the "question mark operator", and is just ?. After a function that returns a result, you can add ?. This will:

  • return what is inside the Result if it is Ok
  • pass the error back if it is Err

In other words, it does almost everything for you.

We can try this with .parse() again. We will write a function called parse_str that tries to turn a &str into a i32. It looks like this:

use std::num::ParseIntError;

fn parse_str(input: &str) -> Result<i32, ParseIntError> {
    let parsed_number = input.parse::<i32>()?; // Here is the question mark
    Ok(parsed_number)
}

fn main() {}

This function takes a &str. If it is Ok, it gives an i32 wrapped in Ok. If it is an Err, it returns a ParseIntError. Then we try to parse the number, and add ?. That means "check if it is an error, and give what is inside the Result if it is okay". If it is not okay, it will return the error and end. But if it is okay, it will go to the next line. On the next line is the number inside of Ok(). We need to wrap it in Ok because the return is Result<i32, ParseIntError>, not i32.

Now, we can try out our function. Let's see what it does with a vec of &strs.

fn parse_str(input: &str) -> Result<i32, std::num::ParseIntError> {
    let parsed_number = input.parse::<i32>()?;
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item);
        println!("{:?}", parsed);
    }
}

This prints:

Err(ParseIntError { kind: InvalidDigit })
Ok(8)
Err(ParseIntError { kind: InvalidDigit })
Err(ParseIntError { kind: InvalidDigit })
Ok(6060)

How did we find std::num::ParseIntError? One easy way is to "ask" the compiler again.

fn main() {
    let failure = "Not a number".parse::<i32>();
    failure.rbrbrb(); // ⚠️ Compiler: "What is rbrbrb()???"
}

The compiler doesn't understand, and says:

error[E0599]: no method named `rbrbrb` found for enum `std::result::Result<i32, std::num::ParseIntError>` in the current scope
 --> src\main.rs:3:13
  |
3 |     failure.rbrbrb();
  |             ^^^^^^ method not found in `std::result::Result<i32, std::num::ParseIntError>`

So std::result::Result<i32, std::num::ParseIntError> is the signature we need.

We don't need to write std::result::Result because Result is always "in scope" (in scope = ready to use). Rust does this for all the types we use a lot so we don't have to write std::result::Result, std::collections::Vec, etc.

We aren't working with things like files yet, so the ? operator doesn't look too useful yet. But here is a useless but quick example that shows how you can use it on a single line. Instead of making an i32 with .parse(), we'll do a lot more. We'll make an u16, then turn it to a String, then a u32, then to a String again, and finally to a i32.

use std::num::ParseIntError;

fn parse_str(input: &str) -> Result<i32, ParseIntError> {
    let parsed_number = input.parse::<u16>()?.to_string().parse::<u32>()?.to_string().parse::<i32>()?; // Add a ? each time to check and pass it on
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item);
        println!("{:?}", parsed);
    }
}

This prints the same thing, but this time we handled three Results in a single line. Later on we will do this with files, because they always return Results because many things can go wrong.

Imagine the following: you want to open a file, write to it, and close it. First you need to successfully find the file (that's a Result). Then you need to successfully write to it (that's a Result). With ? you can do that on one line.

When panic and unwrap are good

Rust has a panic! macro that you can use to make it panic. It is easy to use:

fn main() {
    panic!("Time to panic!");
}

The message "Time to panic!" displays when you run the program: thread 'main' panicked at 'Time to panic!', src\main.rs:2:3

You will remember that src\main.rs is the directory and file name, and 2:3 is the line and column name. With this information, you can find the code and fix it.

panic! is a good macro to use to make sure that you know when something changes. For example, this function called prints_three_things always prints index [0], [1], and [2] from a vector. It is okay because we always give it a vector with three items:

fn prints_three_things(vector: Vec<i32>) {
    println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
    let my_vec = vec![8, 9, 10];
    prints_three_things(my_vec);
}

It prints 8, 9, 10 and everything is fine.

But imagine that later on we write more and more code, and forget that my_vec can only be three things. Now my_vec in this part has six things:

fn prints_three_things(vector: Vec<i32>) {
  println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
  let my_vec = vec![8, 9, 10, 10, 55, 99]; // Now my_vec has six things
  prints_three_things(my_vec);
}

No error happens, because [0] and [1] and [2] are all inside this longer Vec. But what if it was really important to only have three things? We wouldn't know that there was a problem because the program doesn't panic. We should have done this instead:

fn prints_three_things(vector: Vec<i32>) {
    if vector.len() != 3 {
        panic!("my_vec must always have three items") // will panic if the length is not 3
    }
    println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
    let my_vec = vec![8, 9, 10];
    prints_three_things(my_vec);
}

Now we will know if the vector has six items because it panics as it should:

    // ⚠️
fn prints_three_things(vector: Vec<i32>) {
    if vector.len() != 3 {
        panic!("my_vec must always have three items")
    }
    println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
    let my_vec = vec![8, 9, 10, 10, 55, 99];
    prints_three_things(my_vec);
}

This gives us thread 'main' panicked at 'my_vec must always have three items', src\main.rs:8:9. Thanks to panic!, we now remember that my_vec should only have three items. So panic! is a good macro to create reminders in your code.

There are three other macros that are similar to panic! that you use a lot in testing. They are: assert!, assert_eq!, and assert_ne!.

Here is what they mean:

  • assert!(): if the part inside () is not true, the program will panic.
  • assert_eq!(): the two items inside () must be equal.
  • assert_ne!(): the two items inside () must not be equal. (ne means not equal)

Some examples:

fn main() {
    let my_name = "Loki Laufeyson";

    assert!(my_name == "Loki Laufeyson");
    assert_eq!(my_name, "Loki Laufeyson");
    assert_ne!(my_name, "Mithridates");
}

This will do nothing, because all three assert macros are okay. (This is what we want)

You can also add a message if you want.

fn main() {
    let my_name = "Loki Laufeyson";

    assert!(
        my_name == "Loki Laufeyson",
        "{} should be Loki Laufeyson",
        my_name
    );
    assert_eq!(
        my_name, "Loki Laufeyson",
        "{} and Loki Laufeyson should be equal",
        my_name
    );
    assert_ne!(
        my_name, "Mithridates",
        "You entered {}. Input must not equal Mithridates",
        my_name
    );
}

These messages will only display if the program panics. So if you run this:

fn main() {
    let my_name = "Mithridates";

    assert_ne!(
        my_name, "Mithridates",
        "You enter {}. Input must not equal Mithridates",
        my_name
    );
}

It will display:

thread 'main' panicked at 'assertion failed: `(left != right)`
  left: `"Mithridates"`,
 right: `"Mithridates"`: You entered Mithridates. Input must not equal Mithridates', src\main.rs:4:5

So it is saying "you said that left != right, but left == right". And it displays our message that says You entered Mithridates. Input must not equal Mithridates.

unwrap is also good when you are writing your program and you want it to crash when there is a problem. Later, when your code is finished it is good to change unwrap to something else that won't crash.

You can also use expect, which is like unwrap but a bit better because you give it your own message. Textbooks usually give this advice: "If you use .unwrap() a lot, at least use .expect() for better error messages."

This will crash:

   // ⚠️
fn get_fourth(input: &Vec<i32>) -> i32 {
    let fourth = input.get(3).unwrap();
    *fourth
}

fn main() {
    let my_vec = vec![9, 0, 10];
    let fourth = get_fourth(&my_vec);
}

The error message is thread 'main' panicked at 'called Option::unwrap() on a None value', src\main.rs:7:18.

Now we write our own message with expect:

   // ⚠️
fn get_fourth(input: &Vec<i32>) -> i32 {
    let fourth = input.get(3).expect("Input vector needs at least 4 items");
    *fourth
}

fn main() {
    let my_vec = vec![9, 0, 10];
    let fourth = get_fourth(&my_vec);
}

It crashes again, but the error is better: thread 'main' panicked at 'Input vector needs at least 4 items', src\main.rs:7:18. .expect() is a little better than .unwrap() because of this, but it will still panic on None. Now here is an example of a bad practice, a function that tries to unwrap two times. It takes a Vec<Option<i32>>, so maybe each part will have a Some<i32> or maybe a None.

fn try_two_unwraps(input: Vec<Option<i32>>) {
    println!("Index 0 is: {}", input[0].unwrap());
    println!("Index 1 is: {}", input[1].unwrap());
}

fn main() {
    let vector = vec![None, Some(1000)]; // This vector has a None, so it will panic
    try_two_unwraps(vector);
}

The message is: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src\main.rs:2:32. We're not sure if it was the first .unwrap() or the second .unwrap() until we check the line. It would be better to check the length and also to not unwrap. But with .expect() at least it will be a little better. Here it is with .expect():

fn try_two_unwraps(input: Vec<Option<i32>>) {
    println!("Index 0 is: {}", input[0].expect("The first unwrap had a None!"));
    println!("Index 1 is: {}", input[1].expect("The second unwrap had a None!"));
}

fn main() {
    let vector = vec![None, Some(1000)];
    try_two_unwraps(vector);
}

So that is a bit better: thread 'main' panicked at 'The first unwrap had a None!', src\main.rs:2:32. We have the line number as well so we can find it.

You can also use unwrap_or if you want to always have a value that you want to choose. If you do this it will never panic. That's:

    1. good because your program won't panic, but
    1. maybe not good if you want the program to panic if there's a problem.

But usually we don't want our program to panic, so unwrap_or is a good method to use.

fn main() {
    let my_vec = vec![8, 9, 10];

    let fourth = my_vec.get(3).unwrap_or(&0); // If .get doesn't work, we will make the value &0.
                                              // .get returns a reference, so we need &0 and not 0
                                              // You can write "let *fourth" with a * if you want fourth to be
                                              // a 0 and not a &0, but here we just print so it doesn't matter

    println!("{}", fourth);
}

This prints 0 because .unwrap_or(&0) gives a 0 even if it is a None.

Traits

We have seen traits before: Debug, Copy, Clone are all traits. To give a type a trait, you have to implement it. Because Debug and the others are so common, we have attributes that automatically do it. That's what happens when you write #[derive(Debug)]: you are automatically implementing Debug.

#[derive(Debug)]
struct MyStruct {
    number: usize,
}

fn main() {}

But other traits are more difficult, so you need to implement them manually with impl. For example, Add (found at std::ops::Add) is used to add two things. But Rust doesn't know exactly how you want to add things, so you have to tell it.

struct ThingsToAdd {
    first_thing: u32,
    second_thing: f32,
}

fn main() {}

We can add first_thing and second_thing, but we need to give more information. Maybe we want an f32, so something like this:

// 🚧
let result = self.second_thing + self.first_thing as f32

But maybe we want an integer, so like this:

// 🚧
let result = self.second_thing as u32 + self.first_thing

Or maybe we want to just put self.first_thing next to self.second_thing and say that this is how we want to add. So if we add 55 to 33.4, we want to see 5533.4, not 88.4.

So first let's look at how to make a trait. The important thing to remember about traits is that they are about behaviour. To make a trait, write trait and then create some functions.

struct Animal { // A simple struct - an Animal only has a name
    name: String,
}

trait Dog { // The dog trait gives some functionality
    fn bark(&self) { // It can bark
        println!("Woof woof!");
    }
    fn run(&self) { // and it can run
        println!("The dog is running!");
    }
}

impl Dog for Animal {} // Now Animal has the trait Dog

fn main() {
    let rover = Animal {
        name: "Rover".to_string(),
    };

    rover.bark(); // Now Animal can use bark()
    rover.run();  // and it can use run()
}

This is okay, but we don't want to print "The dog is running". You can change the methods that a trait gives you if you want, but you have to have the same signature. That means that it needs to take the same things, and return the same things. For example, we can change the method .run(), but we have to follow the signature. The signature says:

// 🚧
fn run(&self) {
    println!("The dog is running!");
}

fn run(&self) means "fn run() takes &self, and returns nothing". So you can't do this:

fn run(&self) -> i32 { // ⚠️
    5
}

Rust will say:

   = note: expected fn pointer `fn(&Animal)`
              found fn pointer `fn(&Animal) -> i32`

But we can do this:

struct Animal { // A simple struct - an Animal only has a name
    name: String,
}

trait Dog { // The dog trait gives some functionality
    fn bark(&self) { // It can bark
        println!("Woof woof!");
    }
    fn run(&self) { // and it can run
        println!("The dog is running!");
    }
}

impl Dog for Animal {
    fn run(&self) {
        println!("{} is running!", self.name);
    }
}

fn main() {
    let rover = Animal {
        name: "Rover".to_string(),
    };

    rover.bark(); // Now Animal can use bark()
    rover.run();  // and it can use run()
}

Now it prints Rover is running!. This is okay because we are returning (), or nothing, which is what the trait says.

When you are writing a trait, you can just write the function signature. But if you do that, the user will have to write the function. Let's try that. Now we change bark() and run() to just say fn bark(&self); and fn run(&self);. This is not a full function, so the user must write it.

struct Animal {
    name: String,
}

trait Dog {
    fn bark(&self); // bark() says it needs a &self and returns nothing
    fn run(&self); // run() says it needs a &self and returns nothing.
                   // So now we have to write them ourselves.
}

impl Dog for Animal {
    fn bark(&self) {
        println!("{}, stop barking!!", self.name);
    }
    fn run(&self) {
        println!("{} is running!", self.name);
    }
}

fn main() {
    let rover = Animal {
        name: "Rover".to_string(),
    };

    rover.bark();
    rover.run();
}

So when you create a trait, you must think: "Which functions should I write? And which functions should the user write?" If you think the user should use the function the same way every time, then write out the function. If you think the user will use it differently, then just write the function signature.

So let's try implementing the Display trait for our struct. First we will make a simple struct:

struct Cat {
    name: String,
    age: u8,
}

fn main() {
    let mr_mantle = Cat {
        name: "Reggie Mantle".to_string(),
        age: 4,
    };
}

Now we want to print mr_mantle. Debug is easy to derive:

#[derive(Debug)]
struct Cat {
    name: String,
    age: u8,
}

fn main() {
    let mr_mantle = Cat {
        name: "Reggie Mantle".to_string(),
        age: 4,
    };

    println!("Mr. Mantle is a {:?}", mr_mantle);
}

but Debug print is not the prettiest way to print, because it looks like this.

Mr. Mantle is a Cat { name: "Reggie Mantle", age: 4 }

So we need to implement Display for Cat if we want nicer printing. On https://doc.rust-lang.org/std/fmt/trait.Display.html we can see the information for Display, and one example. It says:

use std::fmt;

struct Position {
    longitude: f32,
    latitude: f32,
}

impl fmt::Display for Position {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.longitude, self.latitude)
    }
}

fn main() {}

Some parts of this we don't understand yet, like <'_> and what f is doing. But we understand the Position struct: it is just two f32s. We also understand that self.longitude and self.latitude are the fields in the struct. So maybe we can just use this code for our struct, with self.name and self.age. Also, write! looks a lot like println! so it is pretty familiar. So we write this:

use std::fmt;

struct Cat {
    name: String,
    age: u8,
}

impl fmt::Display for Cat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} is a cat who is {} years old.", self.name, self.age)
    }
}

fn main() {}

Let's add a fn main(). Now our code looks like this:

use std::fmt;

struct Cat {
    name: String,
    age: u8,
}

impl fmt::Display for Cat {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
      write!(f, "{} is a cat who is {} years old.", self.name, self.age)
  }
}

fn main() {
    let mr_mantle = Cat {
        name: "Reggie Mantle".to_string(),
        age: 4,
    };

    println!("{}", mr_mantle);
}

Success! Now when we use {} to print, we get Reggie Mantle is a cat who is 4 years old.. This looks much better.

By the way, if you implement Display then you get the ToString trait for free. That's because you use the format! macro for the .fmt() function, which lets you make a String with .to_string(). So we could do something like this where we pass reggie_mantle to a function that wants a String, or anything else.

use std::fmt;
struct Cat {
    name: String,
    age: u8,
}

impl fmt::Display for Cat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} is a cat who is {} years old.", self.name, self.age)
    }
}

fn print_cats(pet: String) {
    println!("{}", pet);
}

fn main() {
    let mr_mantle = Cat {
        name: "Reggie Mantle".to_string(),
        age: 4,
    };

    print_cats(mr_mantle.to_string()); // Turn him into a String here
    println!("Mr. Mantle's String is {} letters long.", mr_mantle.to_string().chars().count()); // Turn him into chars and count them
}

This prints:

Reggie Mantle is a cat who is 4 years old.
Mr. Mantle's String is 42 letters long.

The thing to remember about traits is that they are about the behaviour of something. How does your struct act? What can it do? That's what traits are for. If you think of some of the traits we've seen so far, they are all about behaviour: Copy is something that a type can do. Display is also something that a type can do. ToString is another trait, and it's also something that a type can do: it can change into a String. In our Dog trait the word dog doesn't mean something you can do, but it gives some methods that let it do things. You could also implement it for a struct Poodle or struct Beagle and they would all get Dog methods.

Let's look at another example that is even more connected to just behaviour. We'll imagine a fantasy game with some simple characters. One is a Monster, the other two are Wizard and Ranger. The Monster just has health so we can attack it, the other two don't have anything yet. But we made two traits. One is called FightClose, and lets you fight up close. The other is FightFromDistance, and lets you fight from far away. Only Ranger can use FightFromDistance. Here's what it looks like:

struct Monster {
    health: i32,
}

struct Wizard {}
struct Ranger {}

trait FightClose {
    fn attack_with_sword(&self, opponent: &mut Monster) {
        opponent.health -= 10;
        println!(
            "You attack with your sword. Your opponent now has {} health left.",
            opponent.health
        );
    }
    fn attack_with_hand(&self, opponent: &mut Monster) {
        opponent.health -= 2;
        println!(
            "You attack with your hand. Your opponent now has {} health left.",
            opponent.health
        );
    }
}
impl FightClose for Wizard {}
impl FightClose for Ranger {}

trait FightFromDistance {
    fn attack_with_bow(&self, opponent: &mut Monster, distance: u32) {
        if distance < 10 {
            opponent.health -= 10;
            println!(
                "You attack with your bow. Your opponent now has {} health left.",
                opponent.health
            );
        }
    }
    fn attack_with_rock(&self, opponent: &mut Monster, distance: u32) {
        if distance < 3 {
            opponent.health -= 4;
        }
        println!(
            "You attack with your rock. Your opponent now has {} health left.",
            opponent.health
        );
    }
}
impl FightFromDistance for Ranger {}

fn main() {
    let radagast = Wizard {};
    let aragorn = Ranger {};

    let mut uruk_hai = Monster { health: 40 };

    radagast.attack_with_sword(&mut uruk_hai);
    aragorn.attack_with_bow(&mut uruk_hai, 8);
}

This prints:

You attack with your sword. Your opponent now has 30 health left.
You attack with your bow. Your opponent now has 20 health left.

We pass self inside our trait all the time, but we can't do much with it right now. That's because Rust doesn't know what type is going to use it. It could be a Wizard, it could be a Ranger, it could be a new struct called Toefocfgetobjtnode or anything else. To give self some functionality, we can add necessary traits to the trait. If we want to print with {:?} for example then we need Debug. You can add it to the trait just by writing it after : (a colon). Now our code looks like this:

struct Monster {
    health: i32,
}

#[derive(Debug)] // Now Wizard has Debug
struct Wizard {
    health: i32, // Now Wizard has health
}
#[derive(Debug)] // So does Ranger
struct Ranger {
    health: i32, // So does Ranger
}

trait FightClose: std::fmt::Debug { // Now a type needs Debug to use FightClose
    fn attack_with_sword(&self, opponent: &mut Monster) {
        opponent.health -= 10;
        println!(
            "You attack with your sword. Your opponent now has {} health left. You are now at: {:?}", // We can now print self with {:?} because we have Debug
            opponent.health, &self
        );
    }
    fn attack_with_hand(&self, opponent: &mut Monster) {
        opponent.health -= 2;
        println!(
            "You attack with your hand. Your opponent now has {} health left.  You are now at: {:?}",
            opponent.health, &self
        );
    }
}
impl FightClose for Wizard {}
impl FightClose for Ranger {}

trait FightFromDistance: std::fmt::Debug { // We could also do trait FightFromDistance: FightClose because FightClose needs Debug
    fn attack_with_bow(&self, opponent: &mut Monster, distance: u32) {
        if distance < 10 {
            opponent.health -= 10;
            println!(
                "You attack with your bow. Your opponent now has {} health left.  You are now at: {:?}",
                opponent.health, self
            );
        }
    }
    fn attack_with_rock(&self, opponent: &mut Monster, distance: u32) {
        if distance < 3 {
            opponent.health -= 4;
        }
        println!(
            "You attack with your rock. Your opponent now has {} health left.  You are now at: {:?}",
            opponent.health, self
        );
    }
}
impl FightFromDistance for Ranger {}

fn main() {
    let radagast = Wizard { health: 60 };
    let aragorn = Ranger { health: 80 };

    let mut uruk_hai = Monster { health: 40 };

    radagast.attack_with_sword(&mut uruk_hai);
    aragorn.attack_with_bow(&mut uruk_hai, 8);
}

Now this prints:

You attack with your sword. Your opponent now has 30 health left. You are now at: Wizard { health: 60 }
You attack with your bow. Your opponent now has 20 health left.  You are now at: Ranger { health: 80 }

In a real game it might be better to rewrite this for each type, because You are now at: Wizard { health: 60 } looks funny. That's also why methods inside traits are usually simple, because you don't know what type is going to use it. You can't write things like self.0 += 10 for example. But this example shows that we can use other traits inside a trait we are writing. And when we do that, we get some methods that we can use.

One other way to use a trait is with what are called trait bounds. That means "limitations by a trait". Trait bounds are easy because a trait actually doesn't need any methods, or anything at all. Let's rewrite our code with something similar but different. This time our trait doesn't have any methods, but we have other functions that require traits to use.

use std::fmt::Debug;  // So we don't have to write std::fmt::Debug every time now

struct Monster {
    health: i32,
}

#[derive(Debug)]
struct Wizard {
    health: i32,
}
#[derive(Debug)]
struct Ranger {
    health: i32,
}

trait Magic{} // No methods for any of these traits. They are just trait bounds
trait FightClose {}
trait FightFromDistance {}

impl FightClose for Ranger{} // Each type gets FightClose,
impl FightClose for Wizard {}
impl FightFromDistance for Ranger{} // but only Ranger gets FightFromDistance
impl Magic for Wizard{}  // and only Wizard gets Magic

fn attack_with_bow<T: FightFromDistance + Debug>(character: &T, opponent: &mut Monster, distance: u32) {
    if distance < 10 {
        opponent.health -= 10;
        println!(
            "You attack with your bow. Your opponent now has {} health left.  You are now at: {:?}",
            opponent.health, character
        );
    }
}

fn attack_with_sword<T: FightClose + Debug>(character: &T, opponent: &mut Monster) {
    opponent.health -= 10;
    println!(
        "You attack with your sword. Your opponent now has {} health left. You are now at: {:?}",
        opponent.health, character
    );
}

fn fireball<T: Magic + Debug>(character: &T, opponent: &mut Monster, distance: u32) {
    if distance < 15 {
        opponent.health -= 20;
        println!("You raise your hands and cast a fireball! Your opponent now has {} health left. You are now at: {:?}",
    opponent.health, character);
    }
}

fn main() {
    let radagast = Wizard { health: 60 };
    let aragorn = Ranger { health: 80 };

    let mut uruk_hai = Monster { health: 40 };

    attack_with_sword(&radagast, &mut uruk_hai);
    attack_with_bow(&aragorn, &mut uruk_hai, 8);
    fireball(&radagast, &mut uruk_hai, 8);
}

This prints almost the same thing:

You attack with your sword. Your opponent now has 30 health left. You are now at: Wizard { health: 60 }
You attack with your bow. Your opponent now has 20 health left.  You are now at: Ranger { health: 80 }
You raise your hands and cast a fireball! Your opponent now has 0 health left. You are now at: Wizard { health: 60 }

So you can see there are many ways to do the same thing when you use traits. It all depends on what makes the most sense for the program that you are writing.

Now let's look at how to implement some of the main traits you will use in Rust.

The From trait

From is a very convenient trait to use, and you know this because you have seen it so much already. With From you can make a String from a &str, but you can make many types from many other types. For example, Vec uses From for the following:

From<&'_ [T]>
From<&'_ mut [T]>
From<&'_ str>
From<&'a Vec<T>>
From<[T; N]>
From<BinaryHeap<T>>
From<Box<[T]>>
From<CString>
From<Cow<'a, [T]>>
From<String>
From<Vec<NonZeroU8>>
From<Vec<T>>
From<VecDeque<T>>

That is a lot of Vec::from() that we have not tried yet. Let's make a few and see what happens.

use std::fmt::Display; // We will make a generic function to print them so we want Display

fn print_vec<T: Display>(input: &Vec<T>) { // Take any Vec<T> if type T has Display
    for item in input {
        print!("{} ", item);
    }
    println!();
}

fn main() {

    let array_vec = Vec::from([8, 9, 10]); // Try from an array
    print_vec(&array_vec);

    let str_vec = Vec::from("What kind of vec will I be?"); // An array from a &str? This will be interesting
    print_vec(&str_vec);

    let string_vec = Vec::from("What kind of vec will a String be?".to_string()); // Also from a String
    print_vec(&string_vec);
}

It prints the following:

8 9 10
87 104 97 116 32 107 105 110 100 32 111 102 32 118 101 99 32 119 105 108 108 32 73 32 98 101 63
87 104 97 116 32 107 105 110 100 32 111 102 32 118 101 99 32 119 105 108 108 32 97 32 83 116 114 105 110 103 32 98 101 63

If you look at the type, the second and third vectors are Vec<u8>, which means the bytes of the &str and the String. So you can see that From is very flexible and used a lot. Let's try it with our own types.

We'll make two structs and then implement From for one of them. One struct will be City, and the other will be Country. We want to be able to do this: let country_name = Country::from(vector_of_cities).

It looks like this:

#[derive(Debug)] // So we can print City
struct City {
    name: String,
    population: u32,
}

impl City {
    fn new(name: &str, population: u32) -> Self { // just a new function
        Self {
            name: name.to_string(),
            population,
        }
    }
}
#[derive(Debug)] // Country also needs to be printed
struct Country {
    cities: Vec<City>, // Our cities go in here
}

impl From<Vec<City>> for Country { // Note: we don't have to write From<City>, we can also do
                                   // From<Vec<City>>. So we can also implement on a type that
                                   // we didn't create
    fn from(cities: Vec<City>) -> Self {
        Self { cities }
    }
}

impl Country {
    fn print_cities(&self) { // function to print the cities in Country
        for city in &self.cities {
            // & because Vec<City> isn't Copy
            println!("{:?} has a population of {:?}.", city.name, city.population);
        }
    }
}

fn main() {
    let helsinki = City::new("Helsinki", 631_695);
    let turku = City::new("Turku", 186_756);

    let finland_cities = vec![helsinki, turku]; // This is the Vec<City>
    let finland = Country::from(finland_cities); // So now we can use From

    finland.print_cities();
}

This prints:

"Helsinki" has a population of 631695.
"Turku" has a population of 186756.

You can see that From is easy to implement from types you didn't create like Vec, i32, and so on. Here is one more example where we create a vector that has two vectors. The first vector holds even numbers, and the second holds odd numbers. With From you can give it a vector of i32s and it will turn it into a Vec<Vec<i32>>: a vector that holds vectors of i32.

use std::convert::From;

struct EvenOddVec(Vec<Vec<i32>>);

impl From<Vec<i32>> for EvenOddVec {
    fn from(input: Vec<i32>) -> Self {
        let mut even_odd_vec: Vec<Vec<i32>> = vec![vec![], vec![]]; // A vec with two empty vecs inside
                                                                    // This is the return value but first we must fill it
        for item in input {
            if item % 2 == 0 {
                even_odd_vec[0].push(item);
            } else {
                even_odd_vec[1].push(item);
            }
        }
        Self(even_odd_vec) // Now it is done so we return it as Self (Self = EvenOddVec)
    }
}

fn main() {
    let bunch_of_numbers = vec![8, 7, -1, 3, 222, 9787, -47, 77, 0, 55, 7, 8];
    let new_vec = EvenOddVec::from(bunch_of_numbers);

    println!("Even numbers: {:?}\nOdd numbers: {:?}", new_vec.0[0], new_vec.0[1]);
}

This prints:

Even numbers: [8, 222, 0, 8]
Odd numbers: [7, -1, 3, 9787, -47, 77, 55, 7]

A type like EvenOddVec is probably better as a generic T so we can use many number types. You can try to make the example generic if you want for practice.

Taking a String and a &str in a function

Sometimes you want a function that can take both a String and a &str. You can do this with generics and the AsRef trait. AsRef is used to give a reference from one type to another type. If you look at the documentation for String, you can see that it has AsRef for many types:

https://doc.rust-lang.org/std/string/struct.String.html

Here are some function signatures for them.

AsRef<str>:

// 🚧
impl AsRef<str> for String

fn as_ref(&self) -> &str

AsRef<[u8]>:

// 🚧
impl AsRef<[u8]> for String

fn as_ref(&self) -> &[u8]

AsRef<OsStr>:

// 🚧
impl AsRef<OsStr> for String

fn as_ref(&self) -> &OsStr

You can see that it takes &self and gives a reference to the other type. This means that if you have a generic type T, you can say that it needs AsRef<str>. If you do that, it will be able to take a &str and a String.

Let's start with the generic function. This doesn't work yet:

fn print_it<T>(input: T) {
    println!("{}", input) // ⚠️
}

fn main() {
    print_it("Please print me");
}

Rust says error[E0277]: T doesn't implement std::fmt::Display. So we will require T to implement Display.

use std::fmt::Display;

fn print_it<T: Display>(input: T) {
    println!("{}", input)
}

fn main() {
    print_it("Please print me");
}

Now it works and prints Please print me. That is good, but T can still be too many things. It can be an i8, an f32 and anything else with just Display. So we add AsRef<str>, and now T needs both AsRef<str> and Display.

use std::fmt::Display;

fn print_it<T: AsRef<str> + Display>(input: T) {
    println!("{}", input)
}

fn main() {
    print_it("Please print me");
    print_it("Also, please print me".to_string());
    // print_it(7); <- This will not print
}

Now it won't take types like i8.

Don't forget that you can use where to write the function differently when it gets long. If we add Debug then it becomes fn print_it<T: AsRef<str> + Display + Debug>(input: T) which is long for one line. So we can write it like this:

use std::fmt::{Debug, Display}; // add Debug

fn print_it<T>(input: T) // Now this line is easy to read
where
    T: AsRef<str> + Debug + Display, // and these traits are easy to read
{
    println!("{}", input)
}

fn main() {
    print_it("Please print me");
    print_it("Also, please print me".to_string());
}

Chaining methods

Rust is a systems programming language like C and C++, and its code can be written as separate commands in separate lines, but it also has a functional style. Both styles are okay, but functional style is usually shorter. Here is an example of the non-functional style (called "imperative style") to make a Vec from 1 to 10:

fn main() {
    let mut new_vec = Vec::new();
    let mut counter = 1;

    while counter < 11 {
        new_vec.push(counter);
        counter += 1;
    }

    println!("{:?}", new_vec);
}

This prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

And here is an example of functional style:

fn main() {
    let new_vec = (1..=10).collect::<Vec<i32>>();
    // Or you can write it like this:
    // let new_vec: Vec<i32> = (1..=10).collect();
    println!("{:?}", new_vec);
}

.collect() can make collections of many types, so we have to tell it the type.

With functional style you can chain methods. "Chaining methods" means to put many methods together in a single statement. Here is an example of many methods chained together:

fn main() {
    let my_vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let new_vec = my_vec.into_iter().skip(3).take(4).collect::<Vec<i32>>();

    println!("{:?}", new_vec);
}

This creates a Vec with [3, 4, 5, 6]. This is a lot of information for one line, so it can help to put each method on a new line. Let's do that to make it easier to read:

fn main() {
    let my_vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let new_vec = my_vec
        .into_iter() // "iterate" over the items (iterate = work with each item inside it). into_iter() gives us owned values, not references
        .skip(3) // skip over three items: 0, 1, and 2
        .take(4) // take the next four: 3, 4, 5, and 6
        .collect::<Vec<i32>>(); // put them in a new Vec<i32>

    println!("{:?}", new_vec);
}

You can use this functional style best when you understand closures and iterators. So we will learn them next.

Iterators

An iterator is a construct that can give you the items in the collection, one at a time. Actually, we have already used iterators a lot: the for loop gives you an iterator. When you want to use an iterator other times, you have to choose what kind:

  • .iter() for an iterator of references
  • .iter_mut() for an iterator of mutable references
  • .into_iter() for an iterator of values (not references)

A for loop is actually just an iterator that owns its values. That's why it can make it mutable and then you can change the values when you use it.

We can use iterators like this:

fn main() {
    let vector1 = vec![1, 2, 3]; // we will use .iter() and .into_iter() on this one
    let vector1_a = vector1.iter().map(|x| x + 1).collect::<Vec<i32>>();
    let vector1_b = vector1.into_iter().map(|x| x * 10).collect::<Vec<i32>>();

    let mut vector2 = vec![10, 20, 30]; // we will use .iter_mut() on this one
    vector2.iter_mut().for_each(|x| *x +=100);

    println!("{:?}", vector1_a);
    println!("{:?}", vector2);
    println!("{:?}", vector1_b);
}

This prints:

[2, 3, 4]
[110, 120, 130]
[10, 20, 30]

The first two we used a method called .map(). This method lets you do something to every item, then pass it on. The last one we used is one called .for_each(). This method just lets you do something to every item. .iter_mut() plus for_each() is basically just a for loop. Inside each method we can give a name to every item (we just called it x) and use that to change it. These are called closures and we will learn about them in the next section.

Let's go over them again, one at a time.

First we used .iter() on vector1 to get references. We added 1 to each, and made it into a new Vec. vector1 is still alive because we only used references: we didn't take by value. Now we have vector1, and a new Vec called vector1_a. Because .map() just passes it on, we needed to use .collect() to make it into a Vec.

Then we used into_iter to get an iterator by value from vector1. This destroys vector1, because that's what into_iter() does. So after we make vector1_b we can't use vector1 again.

Finally we used .iter_mut() for vector2. It is mutable, so we don't need to use .collect() to create a new Vec. Instead, we change the values in the same Vec with mutable references. So vector2 is still there. Because we don't need a new Vec, we use for_each: it's just like a for loop.

How an iterator works

An iterator works by using a method called .next(), which gives an Option. When you use an iterator, Rust calls next() over and over again. If it gets Some, it keeps going. If it gets None, it stops.

Do you remember the assert_eq! macro? In documentation, you see it all the time. Here it is showing how an iterator works.

fn main() {
    let my_vec = vec!['a', 'b', '거', '柳']; // Just a regular Vec

    let mut my_vec_iter = my_vec.iter(); // This is an Iterator type now, but we haven't called it yet

    assert_eq!(my_vec_iter.next(), Some(&'a'));  // Call the first item with .next()
    assert_eq!(my_vec_iter.next(), Some(&'b'));  // Call the next
    assert_eq!(my_vec_iter.next(), Some(&'거')); // Again
    assert_eq!(my_vec_iter.next(), Some(&'柳')); // Again
    assert_eq!(my_vec_iter.next(), None);        // Nothing is left: just None
    assert_eq!(my_vec_iter.next(), None);        // You can keep calling .next() but it will always be None
}

Implementing Iterator for your own struct or enum is not too hard. First let's make a book library and think about it.

#[derive(Debug)] // we want to print it with {:?}
struct Library {
    library_type: LibraryType, // this is our enum
    books: Vec<String>, // list of books
}

#[derive(Debug)]
enum LibraryType { // libraries can be city libraries or country libraries
    City,
    Country,
}

impl Library {
    fn add_book(&mut self, book: &str) { // we use add_book to add new books
        self.books.push(book.to_string()); // we take a &str and turn it into a String, then add it to the Vec
    }

    fn new() -> Self { // this creates a new Library
        Self {
            library_type: LibraryType::City, // most are in the city so we'll choose City
                                             // most of the time
            books: Vec::new(),
        }
    }
}

fn main() {
    let mut my_library = Library::new(); // make a new library
    my_library.add_book("The Doom of the Darksword"); // add some books
    my_library.add_book("Demian - die Geschichte einer Jugend");
    my_library.add_book("구운몽");
    my_library.add_book("吾輩は猫である");

    println!("{:?}", my_library.books); // we can print our list of books
}

That works well. Now we want to implement Iterator for the library so we can use it in a for loop. Right now if we try a for loop, it doesn't work:

for item in my_library {
    println!("{}", item); // ⚠️
}

It says:

error[E0277]: `Library` is not an iterator
  --> src\main.rs:47:16
   |
47 |    for item in my_library {
   |                ^^^^^^^^^^ `Library` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `Library`
   = note: required by `std::iter::IntoIterator::into_iter`

But we can make library into an iterator with impl Iterator for Library. Information on the Iterator trait is here in the standard library: https://doc.rust-lang.org/std/iter/trait.Iterator.html

On the top left of the page it says: Associated Types: Item and Required Methods: next. An "associated type" means "a type that goes together". Our associated type will be String, because we want the iterator to give us Strings.

In the page it has an example that looks like this:

// an iterator which alternates between Some and None
struct Alternate {
    state: i32,
}

impl Iterator for Alternate {
    type Item = i32;

    fn next(&mut self) -> Option<i32> {
        let val = self.state;
        self.state = self.state + 1;

        // if it's even, Some(i32), else None
        if val % 2 == 0 {
            Some(val)
        } else {
            None
        }
    }
}

fn main() {}

You can see that under impl Iterator for Alternate it says type Item = i32. This is the associated type. Our iterator will be for our list of books, which is a Vec<String>. When we call next, it will give us a String. So we will write type Item = String;. That is the associated item.

To implement Iterator, you need to write the fn next() function. This is where you decide what the iterator should do. For our Library, we want it to give us the last books first. So we will match with .pop() which takes the last item off if it is Some. We also want to print " is found!" for each item. Now it looks like this:

#[derive(Debug, Clone)]
struct Library {
    library_type: LibraryType,
    books: Vec<String>,
}

#[derive(Debug, Clone)]
enum LibraryType {
    City,
    Country,
}

impl Library {
    fn add_book(&mut self, book: &str) {
        self.books.push(book.to_string());
    }

    fn new() -> Self {
        Self {
            library_type: LibraryType::City,
            // most of the time
            books: Vec::new(),
        }
    }
}

impl Iterator for Library {
    type Item = String;

    fn next(&mut self) -> Option<String> {
        match self.books.pop() {
            Some(book) => Some(book + " is found!"), // Rust allows String + &str
            None => None,
        }
    }
}

fn main() {
    let mut my_library = Library::new();
    my_library.add_book("The Doom of the Darksword");
    my_library.add_book("Demian - die Geschichte einer Jugend");
    my_library.add_book("구운몽");
    my_library.add_book("吾輩は猫である");

    for item in my_library.clone() { // we can use a for loop now. Give it a clone so Library won't be destroyed
        println!("{}", item);
    }
}

This prints:

吾輩は猫である is found!
구운몽 is found!
Demian - die Geschichte einer Jugend is found!
The Doom of the Darksword is found!

Closures

Closures are like quick functions that don't need a name. Sometimes they are called lambdas. Closures are easy to find because they use || instead of (). They are very common in Rust, and once you learn to use them you will wonder how you lived without them.

You can bind a closure to a variable, and then it looks exactly like a function when you use it:

fn main() {
    let my_closure = || println!("This is a closure");
    my_closure();
}

So this closure takes nothing: || and prints a message: This is a closure.

In between the || we can add input variables and types, like inside () for a function:

fn main() {
    let my_closure = |x: i32| println!("{}", x);

    my_closure(5);
    my_closure(5+5);
}

This prints:

5
10

When the closure becomes more complicated, you can add a code block. Then it can be as long as you want.

fn main() {
    let my_closure = || {
        let number = 7;
        let other_number = 10;
        println!("The two numbers are {} and {}.", number, other_number);
          // This closure can be as long as we want, just like a function.
    };

    my_closure();
}

But closures are special because they can take variables that are outside the closure even if you only write ||. So you can do this:

fn main() {
    let number_one = 6;
    let number_two = 10;

    let my_closure = || println!("{}", number_one + number_two);
    my_closure();
}

So this prints 16. You didn't need to put anything in || because it can just take number_one and number_two and add them.

By the way, that is where the name closure comes from, because they take variables and "enclose" them inside. And if you want to be very correct:

  • a || that doesn't enclose a variable from outside is an "anonymous function". Anonymous means "doesn't have a name". It works more like a regular function.
  • a || that does enclose a variable from outside is a "closure". It "encloses" the variables around it to use them.

But people will often call all || functions closures, so you don't have to worry about the name. We will just say "closure" for anything with a ||, but remember that it can mean an "anonymous function".

Why is it good to know the difference? It's because an anonymous function actually makes the same machine code as a function with a name. They feel "high level", so sometimes people think that the machine code will be complicated. But the machine code that Rust makes from it is just as fast as a regular function.

So let's look at some more things that closures can do. You can also do this:

fn main() {
    let number_one = 6;
    let number_two = 10;

    let my_closure = |x: i32| println!("{}", number_one + number_two + x);
    my_closure(5);
}

This closure takes number_one and number_two. We also gave it a new variable x and said that x is 5. Then it adds all three together to print 21.

Usually you see closures in Rust inside of a method, because it is very convenient to have a closure inside. We saw closures in the last section with .map() and .for_each(). In that section we wrote |x| to bring in the next item in an iterator, and that was a closure.

Here is another example: the unwrap_or method that we know that you can use to give a value if unwrap doesn't work. Before, we wrote: let fourth = my_vec.get(3).unwrap_or(&0);. But there is also an unwrap_or_else method that has a closure inside. So you can do this:

fn main() {
    let my_vec = vec![8, 9, 10];

    let fourth = my_vec.get(3).unwrap_or_else(|| { // try to unwrap. If it doesn't work,
        if my_vec.get(0).is_some() {               // see if my_vec has something at index [0]
            &my_vec[0]                             // Give the number at index 0 if there is something
        } else {
            &0 // otherwise give a &0
        }
    });

    println!("{}", fourth);
}

Of course, a closure can be very simple. You can just write let fourth = my_vec.get(3).unwrap_or_else(|| &0); for example. You don't always need to use a {} and write complicated code just because there is a closure. As long as you put the || in, the compiler knows that you have put in the closure that you need.

The most frequent closure method is maybe .map(). Let's take a look at it again. Here is one way to use it:

fn main() {
    let num_vec = vec![2, 4, 6];

    let double_vec = num_vec        // take num_vec
        .iter()                     // iterate over it
        .map(|number| number * 2)   // for each item, multiply by two
        .collect::<Vec<i32>>();     // then make a new Vec from this
    println!("{:?}", double_vec);
}

Another good example is with .for_each() after .enumerate(). The .enumerate() method gives an iterator with the index number and the item. For example: [10, 9, 8] becomes (0, 10), (1, 9), (2, 8). The type for each item here is (usize, i32). So you can do this:

fn main() {
    let num_vec = vec![10, 9, 8];

    num_vec
        .iter()      // iterate over num_vec
        .enumerate() // get (index, number)
        .for_each(|(index, number)| println!("Index number {} has number {}", index, number)); // do something for each one
}

This prints:

Index number 0 has number 10
Index number 1 has number 9
Index number 2 has number 8

In this case we use for_each instead of map. map is for doing something to each item and passing it on, and for_each is doing something when you see each item. Also, map doesn't do anything unless you use a method like collect.

Actually, this is the interesting thing about iterators. If you try to map without a method like collect, the compiler will tell you that it doesn't do anything. It won't panic, but the compiler will tell you that you didn't do anything.

fn main() {
    let num_vec = vec![10, 9, 8];

    num_vec
        .iter()
        .enumerate()
        .map(|(index, number)| println!("Index number {} has number {}", index, number));

}

It says:

warning: unused `std::iter::Map` that must be used
 --> src\main.rs:4:5
  |
4 | /     num_vec
5 | |         .iter()
6 | |         .enumerate()
7 | |         .map(|(index, number)| println!("Index number {} has number {}", index, number));
  | |_________________________________________________________________________________________^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: iterators are lazy and do nothing unless consumed

This is a warning, so it's not an error: the program runs fine. But why doesn't num_vec do anything? We can look at the types to see.

  • let num_vec = vec![10, 9, 8]; Right now it is a Vec<i32>.
  • .iter() Now it is an Iter<i32>. So it is an iterator with items of i32.
  • .enumerate() Now it is an Enumerate<Iter<i32>>. So it is a type Enumerate of type Iter of i32s.
  • .map() Now it is a type Map<Enumerate<Iter<i32>>>. So it is a type Map of type Enumerate of type Iter of i32s.

All we did was make a more and more complicated structure. So this Map<Enumerate<Iter<i32>>> is a structure that is ready to go, but only when we tell it what to do. Rust does this because it needs to be fast. It doesn't want to do this:

  • iterate over all the i32s in the Vec
  • then enumerate over all the i32s from the iterator
  • then map over all the enumerated i32s

Rust only wants to do one calculation, so it creates the structure and waits. Then if we say .collect::<Vec<i32>>() it knows what to do, and starts moving. This is what iterators are lazy and do nothing unless consumed means. The iterators don't do anything until you "consume" them (use them up).

You can even create complicated things like HashMap using .collect(), so it is very powerful. Here is an example of how to put two vecs into a HashMap. First we make the two vectors, and then we will use .into_iter() on them to get an iterator of values. Then we use the .zip() method. This method takes two iterators and attaches them together, like a zipper. Finally, we use .collect() to make the HashMap.

Here is the code:

use std::collections::HashMap;

fn main() {
    let some_numbers = vec![0, 1, 2, 3, 4, 5]; // a Vec<i32>
    let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // a Vec<&str>

    let number_word_hashmap = some_numbers
        .into_iter()                 // now it is an iter
        .zip(some_words.into_iter()) // inside .zip() we put in the other iter. Now they are together.
        .collect::<HashMap<_, _>>();

    println!("For key {} we get {}.", 2, number_word_hashmap.get(&2).unwrap());
}

This prints:

For key 2 we get two.

You can see that we wrote <HashMap<_, _>> because that is enough information for Rust to decide on the type HashMap<i32, &str>. You can write .collect::<HashMap<i32, &str>>(); if you want, or you can write it like this if you prefer:

use std::collections::HashMap;

fn main() {
    let some_numbers = vec![0, 1, 2, 3, 4, 5]; // a Vec<i32>
    let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // a Vec<&str>
    let number_word_hashmap: HashMap<_, _> = some_numbers  // Because we tell it the type here...
        .into_iter()
        .zip(some_words.into_iter())
        .collect(); // we don't have to tell it here
}

There is another method that is like .enumerate() for chars: char_indices(). (Indices means "indexes"). You use it in the same way. Let's pretend we have a big string that made of 3-digit numbers.

fn main() {
    let numbers_together = "140399923481800622623218009598281";

    for (index, number) in numbers_together.char_indices() {
        match (index % 3, number) {
            (0..=1, number) => print!("{}", number), // just print the number if there is a remainder
            _ => print!("{}\t", number), // otherwise print the number with a tab space
        }
    }
}

This prints 140 399 923 481 800 622 623 218 009 598 281.

|_| in a closure

Sometimes you see |_| in a closure. This means that the closure needs an argument (like x), but you don't want to use it. So |_| means "Okay, this closure takes an argument but I won't give it a name because I don't care about it".

Here is an example of an error when you don't do that:

fn main() {
    let my_vec = vec![8, 9, 10];

    println!("{:?}", my_vec.iter().for_each(|| println!("We didn't use the variables at all"))); // ⚠️
}

Rust says that

error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
  --> src\main.rs:28:36
   |
28 |     println!("{:?}", my_vec.iter().for_each(|| println!("We didn't use the variables at all")));
   |                                    ^^^^^^^^ -- takes 0 arguments
   |                                    |
   |                                    expected closure that takes 1 argument

The compiler actually gives you some help:

help: consider changing the closure to take and ignore the expected argument
   |
28 |     println!("{:?}", my_vec.iter().for_each(|_| println!("We didn't use the variables at all")));

This is good advice. If you change || to |_| then it will work.

Helpful methods for closures and iterators

Rust becomes a very fun to language once you become comfortable with closures. With closures you can chain methods to each other and do a lot of things with very little code. Here are some closures and methods used with closures that we didn't see yet.

.filter(): This lets you keep the items in an iterator that you want to keep. Let's filter the months of the year.

fn main() {
    let months = vec!["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    let filtered_months = months
        .into_iter()                         // make an iter
        .filter(|month| month.len() < 5)     // We don't want months more than 5 bytes in length.
                                             // We know that each letter is one byte so .len() is fine
        .filter(|month| month.contains("u")) // Also we only like months with the letter u
        .collect::<Vec<&str>>();

    println!("{:?}", filtered_months);
}

This prints ["June", "July"].

.filter_map(). This is called filter_map() because it does .filter() and .map(). The closure must return an Option<T>, and then filter_map() takes the value out of each Option if it is Some. So for example if you were to .filter_map() a vec![Some(2), None, Some(3)], it would return [2, 3].

We will write an example with a Company struct. Each company has a name so that field is String, but the CEO might have recently quit. So the ceo field is Option<String>. We will .filter_map() over some companies to just keep the CEO names.

struct Company {
    name: String,
    ceo: Option<String>,
}

impl Company {
    fn new(name: &str, ceo: &str) -> Self {
        let ceo = match ceo {
            "" => None,
            ceo => Some(ceo.to_string()),
        }; // ceo is decided, so now we return Self
        Self {
            name: name.to_string(),
            ceo,
        }
    }

    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone() // Just returns a clone of the CEO (struct is not Copy)
    }
}

fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Doug Suttles"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];

    let all_the_ceos = company_vec
        .into_iter()
        .filter_map(|company| company.get_ceo()) // filter_map needs Option<T>
        .collect::<Vec<String>>();

    println!("{:?}", all_the_ceos);
}

This prints ["Unknown", "Doug Suttles"].

Since .filter_map() needs an Option, what about Result? No problem: there is a method called .ok() that turns Result into Option. It is called .ok() because all it can send is the Ok result (the Err information is gone). You remember that Option is Option<T> while Result is Result<T, E> with information for both Ok and Err. So when you use .ok(), any Err information is lost and it becomes None.

Using .parse() is an easy example for this, where we try to parse some user input. .parse() here takes a &str and tries to turn it into an f32. It returns a Result, but we are using filter_map() so we just throw out the errors. Anything that is Err becomes None and is filtered out by .filter_map().

fn main() {
    let user_input = vec!["8.9", "Nine point nine five", "8.0", "7.6", "eleventy-twelve"];

    let actual_numbers = user_input
        .into_iter()
        .filter_map(|input| input.parse::<f32>().ok())
        .collect::<Vec<f32>>();

    println!("{:?}", actual_numbers);
}

This prints [8.9, 8.0, 7.6].

On the opposite side of .ok() is .ok_or() and ok_or_else(). This turns an Option into a Result. It is called .ok_or() because a Result gives an Ok or an Err, so you have to let it know what the Err value will be. That is because None in an Option doesn't have any information. Also, you can see now that the else part in the names of these methods means that it has a closure.

We can take our Option from the Company struct and turn it into a Result this way. For long-term error handling it is good to create your own type of error. But for now we just give it an error message, so it becomes a Result<String, &str>.

// Everything before main() is exactly the same
struct Company {
    name: String,
    ceo: Option<String>,
}

impl Company {
    fn new(name: &str, ceo: &str) -> Self {
        let ceo = match ceo {
            "" => None,
            ceo => Some(ceo.to_string()),
        };
        Self {
            name: name.to_string(),
            ceo,
        }
    }

    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone()
    }
}

fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Doug Suttles"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];

    let mut results_vec = vec![]; // Pretend we need to gather error results too

    company_vec
        .iter()
        .for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found")));

    for item in results_vec {
        println!("{:?}", item);
    }
}

This line is the biggest change:

// 🚧
.for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found")));

It means: "For each company, use get_ceo(). If you get it, then pass on the value inside Ok. And if you don't, pass on "No CEO found" inside Err. Then push this into the vec."

So when we print results_vec we get this:

Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found")
Err("No CEO found")

So now we have all four entries. Now let's use .ok_or_else() so we can use a closure and get a better error message. Now we have space to use format! to create a String, and put the company name in that. Then we return the String.

// Everything before main() is exactly the same
struct Company {
    name: String,
    ceo: Option<String>,
}

impl Company {
    fn new(name: &str, ceo: &str) -> Self {
        let ceo = match ceo {
            "" => None,
            name => Some(name.to_string()),
        };
        Self {
            name: name.to_string(),
            ceo,
        }
    }

    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone()
    }
}

fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Doug Suttles"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];

    let mut results_vec = vec![];

    company_vec.iter().for_each(|company| {
        results_vec.push(company.get_ceo().ok_or_else(|| {
            let err_message = format!("No CEO found for {}", company.name);
            err_message
        }))
    });

    for item in results_vec {
        println!("{:?}", item);
    }
}

This gives us:

Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found for The Red-Headed League")
Err("No CEO found for Stark Enterprises")

.and_then() is a helpful method that takes an Option, then lets you do something to its value and pass it on. So its input is an Option, and its output is also an Option. It is sort of like a safe "unwrap, then do something, then wrap again".

An easy example is a number that we get from a vec using .get(), because that returns an Option. Now we can pass it to and_then(), and do some math on it if it is Some. If it is None, then the None just gets passed through.

fn main() {
    let new_vec = vec![8, 9, 0]; // just a vec with numbers

    let number_to_add = 5;       // use this in the math later
    let mut empty_vec = vec![];  // results go in here


    for index in 0..5 {
        empty_vec.push(
            new_vec
               .get(index)
                .and_then(|number| Some(number + 1))
                .and_then(|number| Some(number + number_to_add))
        );
    }
    println!("{:?}", empty_vec);
}

This prints [Some(14), Some(15), Some(6), None, None]. You can see that None isn't filtered out, just passed on.

.and() is sort of like a bool for Option. You can match many Options to each other, and if they are all Some then it will give the last one. And if one of them is a None, then it will give None.

First here is a bool example to help imagine. You can see that if you are using && (and), even one false makes everything false.

fn main() {
    let one = true;
    let two = false;
    let three = true;
    let four = true;

    println!("{}", one && three); // prints true
    println!("{}", one && two && three && four); // prints false
}

Now here is the same thing with .and(). Imagine we did five operations and put the results in a Vec<Option<&str>>. If we get a value, we push Some("success!") to the vec. Then we do this two more times. After that we use .and() to only show the indexes that got Some every time.

fn main() {
    let first_try = vec![Some("success!"), None, Some("success!"), Some("success!"), None];
    let second_try = vec![None, Some("success!"), Some("success!"), Some("success!"), Some("success!")];
    let third_try = vec![Some("success!"), Some("success!"), Some("success!"), Some("success!"), None];

    for i in 0..first_try.len() {
        println!("{:?}", first_try[i].and(second_try[i]).and(third_try[i]));
    }
}

This prints:

None
None
Some("success!")
Some("success!")
None

The first one (index 0) is None because there is a None for index 0 in second_try. The second is None because there is a None in first_try. The next is Some("success!") because there is no None for first_try, second try, or third_try.

.any() and .all() are very easy to use in iterators. They return a bool depending on your input. In this example we make a very large vec (about 20,000 items) with all the characters from 'a' to '働'. Then we make a function to check if a character is inside it.

Next we make a smaller vec and ask it whether it is all alphabetic (with the .is_alphabetic() method). Then we ask it if all the characters are less than the Korean character '행'.

Also note that you put a reference in, because .iter() gives a reference and you need a & to compare with another &.

fn in_char_vec(char_vec: &Vec<char>, check: char) {
    println!("Is {} inside? {}", check, char_vec.iter().any(|&char| char == check));
}

fn main() {
    let char_vec = ('a'..'働').collect::<Vec<char>>();
    in_char_vec(&char_vec, 'i');
    in_char_vec(&char_vec, '뷁');
    in_char_vec(&char_vec, '鑿');

    let smaller_vec = ('A'..'z').collect::<Vec<char>>();
    println!("All alphabetic? {}", smaller_vec.iter().all(|&x| x.is_alphabetic()));
    println!("All less than the character 행? {}", smaller_vec.iter().all(|&x| x < '행'));
}

This prints:

Is i inside? true
Is 뷁 inside? false
Is 鑿 inside? false
All alphabetic? false
All less than the character 행? true

By the way, .any() only checks until it finds one matching item, and then it stops. It won't check them all if it has already found a match. If you are going to use .any() on a Vec, it might be a good idea to push the items that might match near the front. Or you can use .rev() after .iter() to reverse the iterator. Here's one vec like that:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);
}

So this Vec has 1000 6 followed by one 5. Let's pretend that we want to use .any() to see if it contains 5. First let's make sure that .rev() is working. Remember, an Iterator always has .next() that lets you check what it does every time.

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);

    let mut iterator = big_vec.iter().rev();
    println!("{:?}", iterator.next());
    println!("{:?}", iterator.next());
}

It prints:

Some(5)
Some(6)

We were right: there is one Some(5) and then the 1000 Some(6) start. So we can write this:

fn main() {
    let mut big_vec = vec![