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.
It is now early August, and *Easy Rust* is almost 300 pages long. I am still writing it so there will be much more content. I plan to finish writing the main content by around August 15. You can contact me here or [on LinkedIn](https://www.linkedin.com/in/davemacleod) if you have any questions. I am a Canadian who lives in Korea, and as I write Easy Rust I think 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.
- [Part 2 - Rust on your computer](#part-2---rust-on-your-computer)
# 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. That's why Part 1 is much longer than Part 1. Then at the end is Part 2, which is 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 of that are working with files or taking user input, graphics, and personal settings. The book makes Part 1 really long so that by the time you finish it 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.
Maybe you don't want to install Rust yet, and that's okay. You can go to [https://play.rust-lang.org/](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 is when you will see samples that go beyond what you can do in the Playground (like opening files).
- 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.
If you want to install Rust, go here [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) and follow the instructions. Usually you will use `rustup` to install and update Rust.
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()`. 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 ⚠️.
// 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.
```rust
fn main() {
let some_number/*: i16*/ = 100;
}
```
To the compiler, both of these look the same.
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 `*/`.
```rust
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...
}
```
Later on when you create your own code and want other people to read it, you will use `///` for the comments. When Rust sees `///` it can automatically put the comments into documents to explain the code.
Rust has simple types that are called **primitive types**. We will start with integers and `char` (characters). Integers are whole numbers with no decimal point. There are two types of integers:
Signs 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 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. (This 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. 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.
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 characters too
}
```
The characters that are used most get numbers that are less than 256, and they can fit into a `u8`. 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:
Fortunately we can easily fix this with `as`. We can't make `i32` a `char`, but we can make a `i32` a `u8`. And then we can make `u8` a `char`. So in one line we use `as` to make my_number a `u8`, and once more to make it a `char`. Now it will compile:
Here is another reason for the different sizes: `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:
All chars are 4 bytes. They are 4 bytes because some characters in a string are more than one byte. Basic letters that have always been on computers are 1 byte, later characters are 2 bytes, and others are 3 and 4. A `char` is 4 bytes so that it can fit any of these.
For example:
```rust
fn main() {
println!("{}", "a".len()); // .len() gives the size in bytes
println!("{}", "ß".len());
println!("{}", "国".len());
println!("{}", "𓅱".len());
}
```
This prints:
```text
1
2
3
4
```
You can see that `a` is one byte, the German `ß` is two, the Japanese `国` is three, and the ancient Egyptian `𓅱` is 4 bytes.
`slice` is six characters in length and six bytes, but `slice2` is three characters in length and seven bytes. `char` needs to fit any character in any language, so it is 4 bytes long.
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.
```rust
fn main() {
let slice = "Hello!";
println!("Slice is {} characters.", slice.chars().count());
let slice2 = "안녕!";
println!("Slice2 is {} characters.", slice2.chars().count());
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. 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`.
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`.
`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.
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. Rust will not compile this if it has a `;`, because the return is `i32` and `;` returns `()`, not `i32`:
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.
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.
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.
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 `{:#?}` (`{:#?}` prints with different formatting).
The compiler says: `error[E0384]: cannot assign twice to immutable variable my_number`. This is because variables are immutable if you only write `let`.
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:
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, we can't see the first `my_number` anymore.
- The stack is very fast, but the heap is not so fast.
- The stack 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.
- Some types don't know the size at compile time. But the stack needs to know the exact size. What do you do? You put the data in the heap, because the heap can have any size of data. And to find it, a pointer goes on the stack, because we always know the size of a pointer.
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. In Rust, references have a `&`. So:
Sometimes you have many `"` and escape characters inside a string, and want Rust to ignore everything. To do this, you can add `r#` to the beginning and `#` to the end. If you need to print `#` 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:
```rust
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 ####
`r#` has another use: with it you can use a keyword as a variable name.
```rust
fn main() {
let r#let = 6; // The variable's name is let
let mut r#mut = 10; // This variable's name is mut
}
```
`r#` also has this function because older versions of Rust didn't have all the same keywords that Rust has now. So with `r#` it's easier to avoid mistakes with variable names that were not keywords before. 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:
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.
We know that `println!` can print with `{}` (for Display) and `{:?}` (for Debug), plus `{:#?}` for pretty printing. But there are many other ways to print.
`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ș`.
- Do you want a variable name? `{:ㅎ^11}` No variable name: it comes 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.
-`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 on the stack:
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ș"));
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.
One other way to make a String is called `.into()` but it is a bit different. 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:
There are two types that don't use `let` to declare: `const` and `static`. Also, you need to write the type for them. These are for variables that don't change (`const` means constant). The difference is that:
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:
`country` is a `String`. We created two references to `country`. They have the type `&String`: a "reference to a String". We could create one hundred references to `country` and it would be no problem.
The function `return_str()` creates a String, then it creates a reference to the string. Then it tries to return the reference. But `country` only lives inside the function. So after the function is over, `country_ref` is referring to memory that is already gone. Rust prevents us from making a mistake with memory.
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. 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 `*` erases one `&`.
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. 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. This is fine, because nobody can change the data.
Situation three: The Employee gives his manager his login information. Then the employee went to give the presentation to 100 people. This is not fine, because the manager can log in and do anything. Maybe his manager will log into the computer and type 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.
The compiler 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.
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".
- Step 1: We create the `String``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.
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".
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`). `adds_hungary` is the full owner, so it can take `country` as mutable.
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. So you don't need to worry about ownership.
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:
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 needs more memory.
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()`. This creates a clone, and we send the clone to the function. `country` is still alive, so we can use it.
Of course, if the `String` is very large, `.clone()` can use a lot of memory. For example, one `String` can be a whole book, and every time we call `.clone()` it will copy the book. So using `&` for a reference is faster, if you can.
It is important to know 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.
It helps to imagine if you simplify the code. `loop_then_return(number)` gives the result 50, so let's delete it and write `50` instead. Also, now we don't need `number` so we will delete it too. Now it looks like this:
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:
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.
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.
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. The type is written `Vec`.
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".
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. If you add more to a vector than its capacity, it will make its capacity double and copy the items into the new space. This is called reallocation.
For example:
```rust
fn main() {
let mut num_vec = Vec::new();
num_vec.push('a'); // add one character
println!("{}", num_vec.capacity()); // prints 1
num_vec.push('a'); // add one more
println!("{}", num_vec.capacity()); // prints 2
num_vec.push('a'); // add one more
println!("{}", num_vec.capacity()); // prints 4. It has three elements, but capacity is 4
num_vec.push('a'); // add one more
num_vec.push('a'); // add one more // Now we have 5 elements
println!("{}", num_vec.capacity()); // Now capacity is 8
}
```
So this vector has three reallocations: 1 to 2, 2 to 4, and 4 to 8. We can make it faster:
```rust
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
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<_>`.
Tuples in Rust use `()`. We have seen many empty tuples already. `fn do_something() {}` has an empty tuple. Also, when you don't return anything in a function, you actually return an empty tuple.
let (a, b, c) = (str_vec[0], str_vec[1], str_vec[2]);
println!("{:?}", b);
}
```
There are many more collection types, and many more ways to use arrays, vecs, and tuples. We will learn more about them. But first we will learn control flow.
Too much `if`, `else`, and `else if` can be difficult to read. You can use `match` instead. But you must match for every possible result. For example, this will not work:
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".
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.
You can also use `@` to use the value of a `match` expression when you want to. 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.
With structs, you can create your own type. Structs are created with the keyword `struct`. The name of a struct should be in UpperCamelCase (capital letter for each word, no spaces).
The next is a tuple struct, or an unnamed struct. It is "unnamed" because you only need to write the types, not the variable names. Tuple structs are good when you need a simple struct and don't need to remember names.
The third type is the named struct. This is probably the most common struct. In this struct you declare variable names and types inside a `{}` code block.
In a named struct, you separate variables by commas. For the last variable you can add a comma or not - it's up to you. `SizeAndColour` had a comma after `colour`:
Did you notice that we wrote the same thing twice? 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.
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`:
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 `u32`s or `i32`s. Of course, you can make a `Vec<(u32, i32)>` (a vec with `(u32, i32)` tuples) but we only want one. So here you can use an enum. Here is a simple example:
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 an enum, you have to pick one. 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`.
You can get the values from a struct or enum by using `let` backwards. This is called `destructuring`, and gives you the values separately. First a simple example:
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.
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.
With loops you can tell Rust to continue something until you want it to stop. With `loop` you can start a loop that does not stop, unless you tell it when to `break`.
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:
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.
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.
Rust also suggests `_number`. 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".
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.
Now that we know how to use loops, here is a better solution to the `match` problem with colours. It is a better solution because we want to compare everything, and a `for` loop looks at every item.
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.
- Regular methods: these take **self** (or **&self** or **&mut self**). Regular methods use a `.`. `.clone()` is a regular method.
- Associated methods (or "static" methods): these do not take self. They are written differently, using `::`. `String::from()` is an associated method. You usually use associated methods 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. 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.
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. 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:
```rust
use std::collections::HashMap; // You have to bring HashMap in to use it
struct City {
name: String,
population: HashMap<u32,u32>, // This will have the date and the population for the date
}
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 tuple with two items
println!("In the year {} the city of {} had a population of {}.", year, tallinn.name, population);
}
}
```
This prints:
```text
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:
```text
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.
```
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 will notice that it is almost the same code.
```rust
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.iter() { // just add .iter() - it will be sorted
println!("In the year {} the city of {} had a population of {}.", year, tallinn.name, population);
}
}
```
Now it will always print:
```text
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.
You can get a value in a `HashMap` by just putting the key in `[]` square brackets. This 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`. Then you will get `None` instead of crashing the program.
If a `HashMap` already has a key when you try to put it in, it will overwrite the value that matches it:
```rust
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, "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`:
```rust
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() {
book_hashmap.insert(1, "Le Petit Prince");
}
println!("{:?}", book_hashmap.get(&1));
}
```
On this subject, `HashMap` has a very interesting method called `.entry()`. With this 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. First is an example where we just insert `true` every time we insert a book title into the `HashMap`.
```rust
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
But 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`:
The interesting part is that it returns a `mut` reference. That means you can bind 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 we will use `+= 1` on the reference. Now it looks like this:
```rust
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 return_value = book_hashmap.entry(book).or_insert(0);
*return_value +=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 variable, you get `book_hashmap.entry(book).or_insert(0)`. If you do that then the mutable reference just doesn't go anywhere: it inserts 0, and then has a mutable reference to 0 that nobody takes. 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:
```text
"L\'Allemagne Moderne", 1
"Eye of the World", 2
"Le Petit Prince", 1
```
You can also use `.or_insert_with()` which lets you use a closure. You can always just do this:
if book == "Eye of the World" { // Maybe an extra copy is arriving next week
// so we know there will be at least one more
1
} else {
0
}
},
);
*return_value += 1;
}
```
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 politican. 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:
```rust
use std::collections::HashMap;
fn main() {
let data = [ // 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.iter() { // This gives a tuple of &(&str, i32)
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.
A `HashSet` is actually a `HashMap` that only has keys. On [the page for HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html) it explains this on the top:
`A hash set implemented as a HashMap where the value is ().`
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.
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:
A `BinaryHeap` is an interesting collection type, because it is mostly unordered but has a bit of order. It is a collection that keeps the largest item in the front, but the other items are in any order.
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 tasks. 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.
```rust
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:
```text
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
A `VecDeque` is a `Vec` that is good at popping items both off the front and the back. 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:
```rust
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.
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 a better solution.
In this 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.
For generics, you use angle brackets with the type inside: `<T>` This means "any type you put into the function". Usually, generics uses types with one capital letter (T, U, V, etc.).
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`.
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 this is a problem:
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: "only accept types that have Debug".
```rust
use std::fmt::Debug; // Debug is located at std::fmt::Debug. If we write this,
So now the compiler knows: "Okay, I will only take a type if it has Debug". Now the code works, because `i32` is Debug. Now we can give it many types: `String`, `&str`, and so on.
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. We want to print them too, so we require Display for U as well.
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.
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 an `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.
The important point to remember: with `Some`, you have a value of type `T` (any type). But with `None`, you don't have anything. So in a `match` statement for Option you can't say:
`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:
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.
This function take 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. We will create our own error types later, because first we need to learn other things.
This is good, but we don't do anything for `None`. 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.
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.
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:
This function takes a `&str`. If it is `Ok`, it gives an `i32` wrapped in `Ok`. If it is an `Err`, it returns a `std::num::ParseIntError`. Then we try to parse the number, and add `?`. That means "check if it is an error, and give 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, std::num::ParseIntError>`, not `i32`.
We don't need to write `std::result::Result` because `Result` is always "in scope" (in scope = ready to use). But `std::num::ParseIntError` is not in scope. We can bring it in scope if we want:
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 when writing your code to make sure that you know when something changes. For example, our function `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:
This gives us `thread 'main' panicked at 'my_vec must always have three items', src\main.rs:8:9`. Now we remember that `my_vec` should only have three items. So `panic!` is a good macro to create reminders in your code.
`unwrap` is also good when you want the program 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 with your own message.
So the error message is `thread 'main' panicked at 'Input vector needs at least 4 items', src\main.rs:7:18`. `.expect()` is a little better than `.unwrap()` because it can give better information, but it will still panic on `None`. 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`.
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()`:
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.
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, it's easy to do:
But other traits are more difficult, so you need to implement them manually with `impl`. For example, std::ops::Add is used to add two things. But you can add in many ways.
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, not 88.
This is okay, but we don't want to print "The dog is running". We can change the method `.run()`, but we have to follow the signature. The signature says:
Actually, a trait doesn't need to write out the whole function. 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.
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 will 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 we need to implement Display for Cat. On [https://doc.rust-lang.org/std/fmt/trait.Display.html](https://doc.rust-lang.org/std/fmt/trait.Display.html) we can see the information for Display, and one example. It says:
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 `f32`s. We also understand that `self.longitude` and `self.latitude` are the values in the struct. So maybe we can just use this for our struct, with `self.name` and `self.age`. Also, `write!` looks a lot like `println!`. So we write this:
*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:
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 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:
```rust
#[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<Cities> 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();
}
```
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 `i32`s and it will turn it into a `Vec<Vec<i32>>`: a vector that holds vectors of `i32`.
```rust
use std::convert::From;
#[derive(Debug)]
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
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.
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:
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`.
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`.
Don't forget that you can 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:
Rust is a systems programming language, but it also has a functional style. Both styles are okay, but functional style is usually shorter. Here is an example of declarative style to make a Vec from 1 to 10:
With functional style you can chain methods. That means to put many methods together in a single statement. Here is an example of many methods chained together:
This creates a Vec with `[3, 4, 5, 6]`. It is a good idea to put each method on a new line if you have many chained methods. This helps you to read the code. Here is the same code with each method on a new line:
An iterator is a collection that can give you the items in the collection, one at a time. Actually, we have already used iterators: the `for` loop gives you an iterator. When you want to use an iterator other times, you have to choose what kind:
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`.
Then 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.
Finally we used `into_iter` to get an iterator by value from `vector1`. This destroys `vector1`, so after we make `vector1_b` we can't use `vector1` again.
An iterator works by using a method called `.next()`, which gives an `Option`. When you use an iterator, Rust calls `next()`. If it gets `Some`, it keeps going. If it gets `None`, it stops.
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:
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](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.
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 it it is `Some`. We also want to print " is found!" for each item. Now it looks like this:
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 `()`.
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".
- a `||` that does enclose a variable from outside is a "closure".
But people will often call all `||` functions closures. After this section we will 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 are easy to write and 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:
Usually you see closures in Rust inside of a method, because it is very convenient to have a closure inside. For example, there is 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:
Of course, a closure can be very simple. You can just write `let fourth = my_vec.get(3).unwrap_or_else(|| &0);` for example. As long as you put the `||` in, the compiler knows that you have put in the closure that you need.
Another good example is with `.enumerate()`. This gives an iterator with the index number, and the item. For example: `[10, 9, 8]` becomes `(0, 10), (1, 9), (2, 8)`. So you can do this:
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:
So this `Map<Enumerate<Iter<i32>>>` is a structure that is ready to go, when we tell it what to do. Rust does this because it needs to be fast. It doesn't want to do this:
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:
```rust
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());
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:
Sometimes you see `|_|` in a closure. This means that the closure needs an argument, but you don't need to use it. So `|_|` means "Okay, here is the argument but I won't give it a name because I don't care about it".
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.
`.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 `Some<String>`. We will `.filter_map()` over some companies to just keep the CEO names.
```rust
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()),
}; // 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)
Since `.filter_map()` needs an `Option`, what about `Result`? No problem: there is a method called `.ok()` that turns `Option` into `Result`. It is called `.ok()` because all it can send is the `Ok` result. 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()` 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()`.
```rust
fn main() {
let user_input = vec!["8.9", "Nine point nine five", "8.0", "7.6", "eleventy-twelve"];
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, and we will do that later. But for now we just give it an error message, so it becomes a `Result<String, &str>`.
```rust
// 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![]; // 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_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:
```text
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`.
`.and_then()` is a helpful message 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.
```rust
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 `Option`s 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`.
```rust
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.
```rust
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];
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 `.is_alphabetic()`). 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 `&`.
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:
```rust
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 anything is 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.
println!("{:?}", big_vec.iter().rev().any(|&number| number == 5));
}
```
And because it's `.rev()`, it only calls `.next()` one time and stops. If we don't use `.rev()` then it will call `.next()` 1001 times before it stops. This code shows it:
```rust
fn main() {
let mut big_vec = vec![6; 1000];
big_vec.push(5);
let mut counter = 0; // Start counting
let mut big_iter = big_vec.into_iter(); // Make it an Iterator
loop {
counter +=1;
if big_iter.next() == Some(5) { // Keep calling .next() until we get Some(5)
break;
}
}
println!("Final counter is: {}", counter);
}
```
`.find()` tells you if an iterator has something, and `.position()` tells you where it is. `.find()` is different from `.any()` because it returns an `Option` with the value inside (or `None`). Meanwhile, `.position()` is also an `Option` with the position number, or `None`. In other words:
-`.find()`: "I'll try to get it for you"
-`.position()`: "I'll try to find where it is for you"
With `.cycle()` you can create an iterator that doesn't stop. This type of iterator works well with `.zip()` to create something new, like this example which creates a `Vec<(i32, &str)>`:
```rust
fn main() {
let even_odd = vec!["even", "odd"];
let even_odd_vec = (0..6)
.zip(even_odd.into_iter().cycle())
.collect::<Vec<(i32,&str)>>();
println!("{:?}", even_odd_vec);
}
```
So even though `.cycle()` might never end, the other iterator only runs six times when zipping them together. That means that the iterator made by `.cycle()` doesn't get a `.next()` call again so it is done. The output is:
Something similar can be done with a range that doesn't have an ending. If you write `0..` then you create a range that never stops. You can use this very easily:
Another popular method is called `.fold()`. This method is used a lot to add together the items in an iterator, but you can also do a lot more. It is somewhat similar to `.for_each()`. In `.fold()`, you first add a starting value (if you are adding items together, then 0), then a comma, then the closure. The closure gives you two items: the total so far, and the next item. First here is a simple example showing `.fold()` to add items together.
-`.take_while()` which takes into an iterator as long as it gets `true` (`take while x > 5` for example)
-`.cloned()` which makes a clone inside the iterator. This turns a reference into a value.
-`.by_ref()` which makes an iterator take a reference. This is good to make sure that you can use a `Vec` or something similar after you use it to make an iterator.
- Many other `_while` methods: `.skip_while()`, `.map_while()`, and so on
This code creates a new mutable number and changes it. Then it creates a vec, and uses `iter` and `map` and `collect` to create a new vec. We can put `dbg!` almost everywhere in this code. `dbg!` asks the compiler: "What are you doing here?"
- String literals: you make these when you write `let my_str = "I am a &str"`. They last for the whole program, because they are written directly into the binary. They have the type `&'static str`. `'` means its lifetime, and string literal have a lifetime called `static`.
- Borrowed str: This is the regular `&str` form without a `static` lifetime. If you create a `String` and get a reference to it, Rust will convert it to a `&str` when you need it. For example:
A lifetime means "how long the variable lives". You only need to think about lifetimes with references. This is because references can't live longer than the object they come from. For example, this function does not work:
The problem is that `my_string` only lives inside `returns_reference`. We try to return `&my_string`, but `&my_string` can't exist without `my_string`. So the compiler says no.
`missing lifetime specifier` means that we need to add a `'` with the lifetime. Then it says that it `contains a borrowed value, but there is no value for it to be borrowed from`. That means that `I am a str` isn't borrowed from anything. It says `consider using the 'static lifetime` by writing `&'static str`. So it thinks we should try saying that this is a string literal.
So now `fn returns_str() -> &'static str` tells Rust: "don't worry, we will only return a string literal". String literals live for the whole program, so Rust is happy.
But `'static` is not the only lifetime. Actually, every variable has a lifetime, but usually we don't have to write it. We only have to write the lifetime when the compiler doesn't know.
Here is an example of another lifetime. Imagine we want to create a `City` struct and give it a `&str` for the name. Later we will have many more `&str`s because we need faster performance than with `String`. So we write it like this, but it won't work:
println!("{} was founded in {}", my_city.name, my_city.date_founded);
}
```
Okay, that works. And maybe this is what you wanted for the struct. However, note that we can only take "string literals", so not references to something else. So this will not work:
```rust
#[derive(Debug)]
struct City {
name: &'static str, // must live for the whole program
date_founded: u32,
}
fn main() {
let city_names = vec!["Ichinomiya".to_string(), "Kurume".to_string()]; // city_names does not live for the whole program
So now we will try what the compiler suggested before. It said to try writing `struct City<'a>` and `name: &'a str`. This means that it will only take a reference for `name` if it lives as long as `City`.
Also remember this important fact: `'a` etc. don't change the actual lifetime of variables. They are like traits for generics. Remember when we wrote generics? For example:
**Interior mutability** means having a little bit of mutability on the inside. Rust has some ways to let you safely change values inside of a struct that is immutable. First, let's look at a simple example where we would want this. Imagine a `struct` called `PhoneModel` with many fields:
It is better for the fields in `PhoneModel` to be immutable, because we don't want the data to change. The `date_issued` and `screen_size` never change, for example.
But inside is one field called `on_sale`. A phone model will first be on sale (`true`), but later the company will stop selling it. Can we make just this one field mutable? Because we don't want to write `let mut super_phone_3000`. If we do, then every field will become mutable.
Rust has many ways to allow some safe mutability inside of something that is immutable. The most simple is called `Cell`. First we use `use std::cell::Cell` so that we can just write `Cell` instead of `std::cell::Cell` every time.
`Cell` works for all types, but works best for simple Copy types because it gives values, not references. `Cell` has a method called `get()` for example that only works on Copy types.
There are many methods for `RefCell`. Two of them are `.borrow()` and `.borrow_mut()`. With these methods, you can do the same thing you do with `&` and `&mut`. The rules are the same:
`Mutex` is another way to change values without declaring `mut`. Mutex means `mutual exclusion`, which means "only one at a time". This is why a `Mutex` is safe, because it only lets one process change it at a time. To do this, it uses `.lock()`. `Lock` is like locking a door from the inside. You go into a room, lock the door, and now you can change things inside the room. Nobody else can come in and stop you, because you locked the door.
But `mutex_changer` still has a lock. How do we stop it? A `Mutex` is unlocked when the `MutexGuard` goes out of scope. "Go out of scope" means the code block is finished. For example:
One other method is `try_lock()`. Then it will try once, and if it doesn't get the lock it will give up. Don't do `try_lock().unwrap()`, because it will panic if it doesn't work. `if let` or `match` is better:
`*my_mutex.lock().unwrap() = 6;` means "unlock my_mutex and make it 6". There is no variable that holds it so you don't need to call `std::mem::drop`. You can do it 100 times if you want - it doesn't matter:
`RwLock` means "read write lock". It is like a `Mutex` but also like a `RefCell`. You use `.write().unwrap()` instead of `.lock().unwrap()` to change it. But you can also use `.read().unwrap()` to get read access. It is like `RefCell` because it follows the rules:
Cow is a very convenient enum. It means "clone on write" and lets you return a `&str` if you don't need a `String`, and a `String` if you need it. (It can also do the same with arrays vs. Vecs, etc.)
You know right away that `'a` means it works with references. The `ToOwned` trait means that it is a type that can be turned into an owned type. For example, `str` is usually a reference (`&str`) and you can turn it into an owned `String`.
Next is `?Sized`. This means "maybe Sized, but maybe not". Almost every type in Rust is Sized, but types like `str` are not. That is why we need a `&` for a `str`, because the compiler doesn't know the size. So if you want a trait that can use something like a `str`, you add `?Sized.`
Imagine that you have a function that returns `Cow<'static, str>`. If you tell the function to return `"My message".into()`, it will look at the type: "My message" is a `str`. This is a `Borrowed` type, so it chooses `Borrowed(&'a B)`. So it becomes `Cow::Borrowed(&'static str)`.
And if you give it a `format!("{}", "My message".into()` then it will look at the type. This time it is a `String`, because `format!` makes a `String`. So this time it will select "Owned".
Here is an example to test `Cow`. We will put a number into a function that returns a `Cow<'static, str>`. Depending on the number, it will create a `&str` or a `String`. Then it uses `.into()` to turn it into a `Cow`. When you do that, it will choose either `Cow:::Borrowed` or `Cow::Owned`. Then we will match to see which one it chose.
A type alias means "giving a new name to another type". Type aliases are very easy. Usually you use them when you have a very long type and don't want to write it every time. It is also good when you want to give a type a better name that is easy to remember. Here are two examples of type aliases.
The type is not difficult, but you want to make your code easier to understand for other people (or for you):
Sometimes you want to write code in general to help you imagine your project. For example, imagine a simple project to do something with books. Here's what you think as you write it:
```rust
struct Book {} // Okay, first I need a book struct.
// Nothing in there yet - will add later
enum BookType { // A book can be hardcover or softcover, so add an enum
But you don't care about `get_book` and `delete_book` right now. This is where you can use `todo!()`. If you add that to the function, Rust will not complain, and will compile.
`todo!()` is actually the same as another macro: `unimplemented!()`. Programmers were using `unimplemented()` a lot but it was long to type, so they created `todo!()` which is shorter.
After `takes_a_string` takes `user_name`, you can't use it anymore. Here that is no problem: you can just give it `user_name.clone()`. But sometimes a variable is part of a struct, and maybe you can't clone the struct. Or maybe the `String` is really long and you don't want to clone it. These are some reasons for `Rc`, which lets you have more than one owner. An `Rc` is like a good office worker: `Rc` writes down who has ownership, and how many. Then once the number of owners goes down to 0, the variable can disappear.
Here's how you use an `Rc`. First imagine two structs: one called `City`, and another called `Cities`. `City` has information for one city, and `Cities` puts all the cities together in `Vec`s.
Of course, it doesn't work because `canada_cities` now owns the data and `calgary` doesn't. We can clone the name: `names: vec![calgary.name.clone()]` but we don't want to clone the `city_history`, which is long. So we can use an `Rc`.
To add a new reference, you have to `clone` the `Rc`. You can clone an item with `item.clone()` or with `Rc::clone(&item)`. So calgary.city_history has 2 owners. We can check the number of owners with `Rc::strong_count(&item)`. Also let's add a new owner. Now our code looks like this:
So if there are strong pointers, are there weak pointers? Yes, there are. Weak pointers are useful because if two Rcs point at each other, they can't die. This is called a "reference cycle". If item 1 has an Rc to item 2, and item 2 has an Rc to item 1, they can't get to 0. In this case you want to use weak references. `Rc` will count the references, but if it only has weak references then it can die. You use `Rc::downgrade(&item)` instead of `Rc::clone(&item)` to make weak references. Also, you use `Rc::weak_count(&item)` to see the weak count.
If you use multiple threads, you can do many things at the same time. Rust uses threads that are called "OS threads". OS thread means the operating system creates the thread on a different core.
You create threads with `std::thread::spawn` and then a closure to tell it what to do. Threads are interesting because they run at the same time. Here is a simple example:
If you run this, it will be different every time. Sometimes it will print, and sometimes it won't print. That is because sometimes `main()` finishes before the thread finishes. And when `main()` finishes, the program is over. This is easier to see in a `for` loop:
But that is a silly way to give the threads time to finish. The better way is to bind the threads to a variable. If you add `let`, then you will create a `JoinHandle`.
`handle` is now a `JoinHandle`, and it has the method `.join()`. This method means "wait until all the threads are done" (it waits for the threads to join it). So now just write `handle.join()` and it will wait for all ten threads to finish.
A closure will try to use `Fn` if it can. But if it needs to change the value it will use `FnMut`, and if it needs to take the whole value, it will use `FnOnce`. `FnOnce` is a good name because it explains what it does: it takes the value once, and then it can't take it again.
It is a long message, but helpful: it says to ``use the `move` keyword``. The problem is that we can do anything to `my_string` while the thread is using it. That would be unsafe.
You can make your own functions that take closures, but inside a function it is less free and you have to decide the type of closure. Outside a function a closure can decide by itself between `Fn`, `FnMut` and `FnOnce`, but inside you have to choose one. The best way to understand is to look at a few function signatures. Here is the one for `.all()`, which we know checks an iterator to see if everything is `true` (depending on what you decide is `true` or `false`). Part of its signature says this:
`fn all<F>`: this tells you that there is a generic type `F`. A closure is always generic because every time it is a different type.
`(&mut self, f: F)`: `&mut self` tells you that it's a method. `f: F` is usually what you see for a closure: this is the variable name and the type. Of course, there is nothing special about `f` and `F` and they could be different names. But in signatures you always always see `f: F`.
Next is the part about the closure: `F: FnMut(Self::Item) -> bool`. Here it decides that the closure is `FnMut`, so it can change the values. It changes the values of `Self::Item`, which is the iterator that it takes. And it has to return a `bool`.
This just says that it takes a closure, takes the value (`FnOnce` = takes the value), and doesn't return anything. So now we can call this closure that takes nothing and do whatever we like. We will create a `Vec` and then iterate over it just to show what we can do now.
```rust
fn do_something<F>(f: F)
where
F: FnOnce(),
{
f();
}
fn main() {
do_something(|| {
let some_vec = vec![9, 8, 10];
some_vec
.iter()
.for_each(|x| println!("The number is: {}", x));
})
}
```
For a more real example, we will create a `City` struct again. This time the `City` struct has more data about years and populations. It has a `Vec<u32>` for all the years, and another `Vec<u32>` for all the populations.
`City` has two functions: `new()` to create a new `City`, and `.city_data()` which has a closure. When we use `.city_data()`, it gives us the years and the populations and a closure, so we can do what we want with the data. The closure type is `FnMut` so we can change the data. It looks like this:
`impl Trait` is similar to generics. You remember that generics use a type `T` (or any other name) which then gets decided when the program compiles. First a concrete type:
```rust
fn gives_higher_i32(one: i32, two: i32) {
let higher = if one > two { one } else { two };
println!("{} is higher.", higher);
}
fn main() {
gives_higher_i32(8, 10);
}
```
This prints: `10 is higher.`.
But this only takes `i32`, so now we will make it generic. We need to compare and we need to print with `{}`, so our type T needs `PartialOrd` and `Display`. Remember, this means "only take types that already have `PartialOrd` and `Display`".
Now let's look at `impl Trait`, which is similar. Instead of a type `T`, we can bring in a type `impl Trait`. Then it will take in a type that implements that trait. It is almost the same:
```rust
fn prints_it(input: impl Into<String> + std::fmt::Display) { // Takes anything that can turn into a String and has Display
println!("You can print many things, including {}", input);
}
fn main() {
let name = "Tuon";
let string_name = String::from("Tuon");
prints_it(name);
prints_it(string_name);
}
```
However, the more interesting part is that we can return `impl Trait`, and that lets us return closures because their function signatures are traits. You can see this in the signatures for methods that have them. For example, this is the signature for `.map()`:
`fn map<B, F>(self, f: F)` mean that it takes two generic types. `F` is a function that takes one item from the container implementing `.map()` and `B` is the return type of that function. Then after the `where` we see the trait bounds. ("Trait bound" means "it must have this trait".) One is `Sized`, but the next is the closure signature. It must be an `FnMut`, and do the closure on `Self::Item`, which is the iterator that you give it. Then it returns `B`.
So we can do the same thing to return a closure. To return a closure, use `impl` and then the closure signature. Once you return it, you can use it just like a function. Here is a small example of a function that gives you a closure depending on the number you put in. If you put 2 or 40 in then it multiplies it, and otherwise it gives you the same number. Because it's a closure we can do anything we want, so we also print a message.
Here is a bit longer example. Let's imagine a game where your character is facing monsters that are stronger at night. We can make an enum called `TimeOfDay` to keep track of the day. Your character is named Simon and has a number called `character_fear`, which is an `f64`. It goes up at night and down during the day. We will create a function called `change_fear` that changes the fear, but also does some other things like write messages. It could look like this:
```rust
enum TimeOfDay { // just a simple enum
Dawn,
Day,
Sunset,
Night,
}
fn change_fear(input: TimeOfDay) -> impl FnMut(f64) -> f64 { // The function takes a TimeOfDay. It returns a closure.
// We use impl FnMut(64) -> f64 to say that it needs to
// change the value, and also gives the same type back.
use TimeOfDay::*; // So we only have to write Dawn, Day, Sunset, Night
// Instead of TimeOfDay::Dawn, TimeOfDay::Day, etc.
match input {
Dawn => |x| { // This is the variable character_fear that we give it later
println!("The morning sun has vanquished the horrible night. You no longer feel afraid.");
println!("Your fear is now {}", x * 0.5);
x * 0.5
},
Day => |x| {
println!("What a nice day. Maybe put your feet up and rest a bit.");
println!("Your fear is now {}", x * 0.2);
x * 0.2
},
Sunset => |x| {
println!("The sun is almost down! This is no good.");
println!("Your fear is now {}", x * 1.4);
x * 1.4
},
Night => |x| {
println!("What a horrible night to have a curse.");
println!("Your fear is now {}", x * 5.0);
x * 5.0
},
}
}
fn main() {
use TimeOfDay::*;
let mut character_fear = 10.0; // Start Simon with 10
let mut daytime = change_fear(Day); // Make four closures here to call every time we want to change Simon's fear.
let mut sunset = change_fear(Sunset);
let mut night = change_fear(Night);
let mut morning = change_fear(Dawn);
character_fear = daytime(character_fear); // Call the closures on Simon's fear. They give a message and change the fear number.
// In real life we would have a Character struct and use it as a method instead,
You remember that we used an `Rc` to give a variable more than one owner. If we are doing the same thing in a thread, we need an `Arc`. `Arc` means "atomic reference counter". Atomic means that it uses the computer's processor so that data only gets written once each time. This is important because if two threads write data at the same time, you will get the wrong result. For example, imagine if you could do this in Rust:
An `Arc` uses the processor to make sure this doesn't happen, so it is the method you must use when you have threads. You don't want an `Arc` for just one thread though, because `Rc` is a bit faster.
Now let's make one more thread. Each thread will do the same thing. You can see that the threads are working at the same time. Sometimes it will say `Thread 1 is working!` first, but other times `Thread 2 is working!` is first. This is called **concurrency**, which means "running together".
Now we want to change the value of `my_number`. Right now it is an `i32`. We will change it to an `Arc<Mutex<i32>>`: an `i32` that can be changed, protected by an `Arc`.
We need to save the handles so we can call `.join()` on each one outside of the loop. If we do this inside the loop, it will wait for the first thread to finish before starting the new one.
This looks complicated but `Arc<Mutex>>` is used very often in Rust, so it becomes natural. Also, you can always write your code to make it cleaner. Here is the same code with one more `use` statement and two functions. The functions don't do anything new, but they move some code out of `main()`. You can try rewriting code like this if it is hard to read.
A channel is an easy way to use many threads that send to one place. You can create a channel in Rust with `std::sync::mpsc`. `mpsc` means "multiple producer, single consumer", so "many threads sending to one place". To start a channel, you use `channel()`. This creates a `Sender` and a `Receiver` that are tied together. You can see this in the function signature:
So you have to choose one name for the sender and one for the receiver. Usually you see `let (sender, receiver) = channel();` to start. Because it's generic, Rust won't know the type if that is all you write:
Now the compiler knows the type. `sender` is a `Result<(), SendError<i32>>` and `receiver` is a `Result<i32, RecvError`. So you can use `unwrap` to see if the sending works, or use better error handling. Let's add `.unwrap()` and also `println!` to see what we get:
A `channel` is like an `Arc` because you can clone it and send the clones into other threads. Let's make two threads and send values to `receiver`. This code will work, but it is not exactly what we want.
The two threads start sending, and then we `println!`. Sometimes it will say `Send a &str this time` and sometimes it will say `And here is another &str`. Let's make a join handle to make them wait.
Now let's pretend that we have a lot of work to do, and want to use threads. We have a big vec with 10,000 items, all 0. We want to change each 0 to a 1. We will use ten threads, and each thread will do one tenth of the work. We will create a new vec and use `.extend()` to put the work in.
It is important to know how to read documentation in Rust so you can understand what other people wrote. Here are some things to know in Rust documentation:
You saw that `assert_eq!` is used when doing testing. You put two items inside the function and the program will panic if they are not equal. Here is a simple example where we need an even number.
Maybe you don't have any plans to use `assert_eq!` in your code, but it is everywhere in Rust documentation. This is because in a document you would need a lot of room to `println!` everything. Also, you would require `Display` or `Debug` for the things you want to print. That's why documentation has `assert_eq!` everywhere. Here is an example from here [https://doc.rust-lang.org/std/vec/struct.Vec.html](https://doc.rust-lang.org/std/vec/struct.Vec.html) showing how to use a Vec:
In these examples, you can just think of `assert_eq!(a, b)` as saying "a is b". Now look at the same example with comments on the right. The comments show what it actually means.
The top bar of a Rust document is the search bar. It shows you results as you type. When you go down a page you can't see the search bar anymore, but if you press `<kbd>s</kbd>` on the keyboard you can search again. So pressing `<kbd>s</kbd>` anywhere lets you search right away.
Usually the code for a method, struct, etc. will not be complete. This is because you don't usually need to see the full source to know how it works, and the full code can be confusing. But if you want to know more, you can click on [src] and see everything. For example, on the page for `String` you can see this signature:
Interesting! Now you can see that a String is a kind of `Vec`. And actually a `String` is a vector of `u8` bytes, which is interesting to know. But you don't need to know that to use the `with_capacity` method so you only see it if you click [src]. So clicking on [src] is a good idea if the document doesn't have much detail and you want to know more.
The important part of the documentation for a trait is "Required Methods" on the left. If you see Required Methods, it probabl means that you have to write the method yourself. For example, for `Iterator` you need to write the `.next()` method. And for `From` you need to write the `.from()` method. But some traits can be implemented with just an **attribute**, like we see in `#[derive(Debug)]`. `Debug` needs the `.fmt()` method, but usually you just use `#[derive(Debug)]` unless you want to do it yourself. That's why the page on `std::fmt::Debug` says that "Generally speaking, you should just derive a Debug implementation."
You have seen code like `#[derive(Debug)]` before: this type of code is called an *attribute*. These attributes are small pieces of code that give information to the compiler. They are not easy to create, but they are very easy to use. If you write an attribute with just `#` then it will affect the code on the next line. But if you write it with `#!` then it will affect everything in its own space.
`#[allow(dead_code)]` and `#[allow(unused_variables)]`. If you write code that you don't use, Rust will still compile but it will let you know. For example, here is a struct with nothing in it and one variables. We don't use either of them.
If you write this, Rust will remind you that you didn't use them:
```text
warning: unused variable: `some_char`
--> src\main.rs:4:9
|
4 | let some_char = 'ん';
| ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_some_char`
|
= note: `#[warn(unused_variables)]` on by default
warning: struct is never constructed: `JustAStruct`
--> src\main.rs:1:8
|
1 | struct JustAStruct {}
| ^^^^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
```
We know that you can write a `_` before the name to make the compiler be quiet:
```rust
struct _JustAStruct {}
fn main() {
let _some_char = 'ん';
}
```
but you can also use attributes. You'll notice in the message that it uses `#[warn(unused_variables)]` and `#[warn(dead_code)]`. In our code, `JustAStruct` is dead code, and `some_char` is an unused variable. The opposite of `warn` is `allow`, so we can write this and it will not say anything:
```rust
#![allow(dead_code)]
#![allow(unused_variables)]
struct Struct1 {}
struct Struct2 {}
struct Struct3 {}
struct Struct4 {}
struct Struct5 {}
fn main() {
let char1 = 'ん';
let char2 = ';';
let some_str = "I'm just a regular &str";
let some_vec = vec!["I", "am", "just", "a", "vec"];
}
```
Of course, dealing with dead code and unused variables is important. But sometimes you want the compiler to be quiet for a while.
`#[derive(TraitName)]` lets you derive some traits for structs and enums that you create. This works with many common traits that can be automatically derived. Some like `Display` can't be automatically derived, because for `Display` you have to choose how to display:
```rust
// ⚠️
#[derive(Display)]
struct HoldsAString {
the_string: String,
}
fn main() {
let my_string = HoldsAString {
the_string: "Here I am!".to_string(),
};
}
```
The error message will tell you that.
```text
error: cannot find derive macro `Display` in this scope
--> src\main.rs:2:10
|
2 | #[derive(Display)]
|
```
But for traits that you can automatically derive, you can put in as many as you like. Let's give `HoldsAString` seven traits in a single line.
Also, you can make a struct `Copy` if its fields are all `Copy`. `HoldsAString` has `String` which is not `Copy` so you can't use `#[derive(Copy)]` for it. But for this struct you can:
#[derive(Clone, Copy)] // You also need Clone to use Copy
struct NumberAndBool {
number: i32, // i32 is Copy
true_or_false: bool // bool is also Copy. So no problem
}
fn does_nothing(input: NumberAndBool) {
}
fn main() {
let number_and_bool = NumberAndBool {
number: 8,
true_or_false: true
};
does_nothing(number_and_bool);
does_nothing(number_and_bool); // If it didn't have copy, this would make an error
}
```
`#[cfg()]` means configuration and tells the compiler whether to run code or not. You see it most like this: `#[cfg(test)]`. You use that when writing test functions so that it knows not to run them unless you are testing. That lets you write your tests close to your code and you don't have to worry about the compiler running them when you don't want it to. One other example using `cfg` is `#[cfg(target_os = "windows")]`. With that you can tell the compiler to only run the code on Windows, or Linux, or anything else.
`#![no_std]` is an interesting attribute that tells Rust not to bring in the standard library. That means you don't have `Vec`, `String`, and anything else in the standard library. You will see this in code for small devices that don't have much memory or space.
You can see many more attributes [here](https://doc.rust-lang.org/reference/attributes.html).
`Box` is a very convenient type in Rust. When you use a `Box`, you can put a type on the heap instead of the stack. To make a new `Box`, just use `Box::new()` and put the item inside.
```rust
fn just_takes_a_variable<T>(item: T) {} // Takes anything and drops it.
just_takes_a_variable(my_box); // because Box is not Copy
}
```
At first it is hard to imagine where to use it, but you use it in Rust a lot. You remember that `&` is used for `str` because the compiler doesn't know the size of a `str`: it can be any length. But the `&` reference is always the same length, so the compiler can use it. `Box` is similar. Also, you can use `*` on a `Box` to get to the value, just like with `&`:
You can also use a Box to create structs with the same struct inside. These are called *recursive*, which means that inside Struct A is maybe another Struct A. Sometimes programmers use these types to create lists, although this type of list is not very popular in Rust. But if you want to create a recursive struct, you can use a `Box`. Here's what happens if you try without a `Box`:
This simple `List` has one item, that may be `Some<List>` (another list), or `None`. Because you can choose `None`, it will not be recursive forever. But the compiler still doesn't know the size:
Now the compiler is fine with the `List`, because everything is behind a `Box`, and it knows the size of a `Box`. Then a very simple list might look like this:
```rust
struct List {
item: Option<Box<List>>,
}
impl List {
fn new() -> List {
List {
item: Some(Box::new(List { item: None })),
}
}
}
fn main() {
let mut my_list = List::new();
}
```
Even without data it is a bit complicated, and Rust does not use this type of pattern very much. This is because Rust has strict rules on borrowing and ownership, as you know. But if you want to start a list like this (a linked list), `Box` can help.
`Box` is very useful for returning traits. You know that you can write traits in generic functions like in this example:
```rust
use std::fmt::Display;
struct DoesntImplementDisplay {}
fn displays_it<T:Display>(input: T) {
println!("{}", input);
}
fn main() {}
```
This only takes something with `Display`, so it can't accept our struct `DoesntImplementDisplay`. But it can take in a lot of others like `String`.
You also saw that we can use `impl Trait` to return other traits, or closures. `Box` can be used in a similar way. You can use a `Box` because otherwise the compiler won't know the size of a value. This example shows that a trait can be used on something of any size:
```rust
#![allow(dead_code)] // Tell the compiler to be quiet
use std::mem::size_of; // This gives the size of a type
trait JustATrait {} // We will implement this on everything
enum EnumOfNumbers {
I8(i8),
AnotherI8(i8),
OneMoreI8(i8),
}
impl JustATrait for EnumOfNumbers {}
struct StructOfNumbers {
an_i8: i8,
another_i8: i8,
one_more_i8: i8,
}
impl JustATrait for StructOfNumbers {}
enum EnumOfOtherTypes {
I8(i8),
AnotherI8(i8),
Collection(Vec<String>),
}
impl JustATrait for EnumOfOtherTypes {}
struct StructOfOtherTypes {
an_i8: i8,
another_i8: i8,
a_collection: Vec<String>,
}
impl JustATrait for StructOfOtherTypes {}
struct ArrayAndI8 {
array: [i8; 1000], // This one will be very large
an_i8: i8,
in_u8: u8,
}
impl JustATrait for ArrayAndI8 {}
fn main() {
println!(
"{}, {}, {}, {}, {}",
size_of::<EnumOfNumbers>(),
size_of::<StructOfNumbers>(),
size_of::<EnumOfOtherTypes>(),
size_of::<StructOfOtherTypes>(),
size_of::<ArrayAndI8>(),
);
}
```
When we print the size of these, we get `2, 3, 32, 32, 1002`. So if you were to do this, it would give an error:
```rust
// ⚠️
fn returns_just_a_trait() -> JustATrait {
let some_enum = EnumOfNumbers::I8(8);
some_enum
}
```
It says:
```text
error[E0746]: return type cannot have an unboxed trait object
--> src\main.rs:53:30
|
53 | fn returns_just_a_trait() -> JustATrait {
| ^^^^^^^^^^ doesn't have a size known at compile-time
```
And this is true, because the size could be 2, 3, 32, 1002, or anything else. So we put it in a `Box` instead. Here we also add the keyword `dyn`. `dyn` is a word that shows you that you are talking about a trait, not a struct or anything else.
So you can change the function to this:
```rust
// 🚧
fn returns_just_a_trait() -> Box<dynJustATrait> {
let some_enum = EnumOfNumbers::I8(8);
Box::new(some_enum)
}
```
And now it works, because on the stack is just a `Box` and we know the size of `Box`.
You see this a lot in the form `Box<dyn Error>`, because sometimes you can have more than one possible error.
We can quickly create two error types to show this. To make an official error type, you have to implement `std::error::Error` for it. That part is easy: just write impl `std::error::Error {}`. But errors also need `Debug` and `Display` so they can give information on the problem. `Debug` is easy with `#[derive(Debug)]` but `Display` needs the `.fmt()` method. We did this once before.
The code looks like this:
```rust
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct ErrorOne;
impl Error for ErrorOne {} // Now it is an error type with Debug. Time for Display:
You can implement a trait called `Default` that will give values to a `struct` or `enum` that you think will be most common. The builder pattern works nicely with this to let users easily make changes when they want. First let's look at `Default`. Actually, most general types in Rust already have `Default`, and they are not surprising: 0, empty strings, `false`, etc.
So `Default` is like the `new` function you usually see but you don't have to enter anything. First we will make a `struct` that doesn't implement `Default` yet. It has a `new` function which we use to make a character named Billy with some stats.
lifestate: if alive { LifeState::Alive } else { LifeState::Dead },
}
}
}
fn main() {
let character_1 = Character::new("Billy".to_string(), 15, 170, 70, true);
}
```
But maybe in our world we want most of the characters to be named Billy, age 15, height 170, weight 70, and alive. We can implement `Default` so that we can just write `Character::default()`. It looks like this:
It prints `The character "Billy" is 15 years old.` Much easier!
Now comes the builder pattern. We will have many Billys, so we will keep the default. But a lot of other characters will be only a bit different. The builder pattern lets us use very small methods to change one value each time. Here is one such method for `Character`:
Make sure to notice that it takes a `mut self`. We saw this once before, and it is not a mutable reference (`&mut self`). It takes ownership of `Self` and with `mut` it will be mutable, even if it wasn't mutable before. That's because `.height()` has full ownership and nobody else can touch it, so it is safe. Then it just changes `self.height` and returns `Self` (which is `Character`).
So let's have three of these builder methods. They are almost the same:
Each one of those changes one variable and gives `Self` back. So now we can write something like this to make a character: `let character_1 = Character::default().height(180).weight(60).name("Bobby");`. If you are building a library for someone else to use, this can make it easy for them. So far our code looks like this:
let character_1 = Character::default().height(180).weight(60).name("Bobby");
println!("{:?}", character_1);
}
```
One last method to add is usually called `.build()`. This method is a sort of final check. When you give a user a method like `.height()` you can make sure that they only put in a `u32()`, but what if they enter 5000 for height? That might not be okay in the game you are making. We will use a final method called `.build()` that returns a `Result`. Inside it we will check if the user input is okay, and if it is, we will return an `Ok(Self)`.
First though let's change the `.new()` method. We don't want users to be free to create any kind of character anymore. So we'll move the values from `impl Default` to `.new()`. And now `.new()` doesn't take any input.
That means we don't need `impl Default` anymore, because `.new()` has all the default values. So we can delete `impl Default`.
Now our code looks like this:
```rust
#[derive(Debug)]
struct Character {
name: String,
age: u8,
height: u32,
weight: u32,
lifestate: LifeState,
}
#[derive(Debug)]
enum LifeState {
Alive,
Dead,
NeverAlive,
Uncertain,
}
impl Character {
fn new() -> Self {
Self {
name: "Billy".to_string(),
age: 15,
height: 170,
weight: 70,
lifestate: LifeState::Alive,
}
}
fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
fn weight(mut self, weight: u32) -> Self {
self.weight = weight;
self
}
fn name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
}
fn main() {
let character_1 = Character::new().height(180).weight(60).name("Bobby");
println!("{:?}", character_1);
}
```
This prints the same thing: `Character { name: "Bobby", age: 15, height: 180, weight: 60, lifestate: Alive }`.
We are almost ready to write the method `.build()`, but there is one problem: how do we make the user use it? Right now a user can write `let x = Character::new().height(76767);` and get a `Character`. There are many ways to do this, and maybe you can imagine your own. But we will add a `can_use: bool` value to `Character`.
can_use: bool, // Set whether the user can use the character
}
\\ Cut other code
fn new() -> Self {
Self {
name: "Billy".to_string(),
age: 15,
height: 170,
weight: 70,
lifestate: LifeState::Alive,
can_use: true, // .new() always gives a good character, so it's true
}
}
```
And for the other methods like `.height()`, we will set `can_use` to `false`. Only `.build()` will set it to `true` again, so now the user has to do a final check with `.build()`. We will make sure that `height` is not above 200 and `weight` is not above 300. Also, in our game there is a bad word called `smurf` that we don't want characters to use.
if self.height <200&&self.weight<300&&!self.name.to_lowercase().contains("smurf"){
self.can_use = true;
Ok(self)
} else {
Err("Could not create character. Characters must have:
1) Height below 200
2) Weight below 300
3) A name that is not Smurf (that is a bad word)"
.to_string())
}
}
```
`!self.name.to_lowercase().contains("smurf")` makes sure that the user doesn't write "SMURF" or "IamSmurf" or something else. It turns the whole `String` into lowercase (small letters), and checks for `.contains()` instead of `==`. And the `!` in front means "not".
If everything is okay, we set `can_use` to `true`, and give the character to the user inside `Ok`.
Now that our code is done, we will create three characters that don't work, and one character that does work. The final code looks like this:
```rust
#[derive(Debug)]
struct Character {
name: String,
age: u8,
height: u32,
weight: u32,
lifestate: LifeState,
can_use: bool, // Here is the new value
}
#[derive(Debug)]
enum LifeState {
Alive,
Dead,
NeverAlive,
Uncertain,
}
impl Character {
fn new() -> Self {
Self {
name: "Billy".to_string(),
age: 15,
height: 170,
weight: 70,
lifestate: LifeState::Alive,
can_use: true, // .new() makes a fine character, so it is true
}
}
fn height(mut self, height: u32) -> Self {
self.height = height;
self.can_use = false; // Now the user can't use the character
self
}
fn weight(mut self, weight: u32) -> Self {
self.weight = weight;
self.can_use = false;
self
}
fn name(mut self, name: &str) -> Self {
self.name = name.to_string();
self.can_use = false;
self
}
fn build(mut self) -> Result<Character,String> {
if self.height <200&&self.weight<300&&!self.name.to_lowercase().contains("smurf"){
self.can_use = true; // Everything is okay, so set to true
Ok(self) // and return the character
} else {
Err("Could not create character. Characters must have:
1) Height below 200
2) Weight below 300
3) A name that is not Smurf (that is a bad word)"
.to_string())
}
}
}
fn main() {
let character_with_smurf = Character::new().name("Lol I am Smurf!!").build(); // This one contains "smurf" - not okay
let character_too_tall = Character::new().height(400).build(); // Too tall - not okay
let character_too_heavy = Character::new().weight(500).build(); // Too heavy - not okay
let okay_character = Character::new()
.name("Billybrobby")
.height(180)
.weight(100)
.build(); // This character is okay. Name is fine, height and weight are fine
// Now they are not Character, they are Result<Character,String>. So let's put them in a Vec so we can see them:
let character_vec = vec![character_with_smurf, character_too_tall, character_too_heavy, okay_character];
for character in character_vec { // Now we will print the character if it's Ok, and print the error if it's Err
`Deref` is the trait that lets you use `*` to deference something. We know that a reference is not the same as a value:
```rust
// ⚠️
fn main() {
let value = 7; // This is an i32
let reference = &7; // This is a &i32
println!("{}", value == reference);
}
```
And Rust won't even give a `false` because it won't even compare the two.
```text
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src\main.rs:4:26
|
4 | println!("{}", value == reference);
| ^^ no implementation for `{integer} == &{integer}`
```
Of course, the answer is `*`. So this will print `true`:
```rust
fn main() {
let value = 7;
let reference = &7;
println!("{}", value == *reference);
}
```
Now let's imagine a simple type that just holds a number. It will be like a `Box`, and we have some ideas for some extra functions for it. But if we just give it a number, it won't be able to do much with it.
We can't use `*` like we can with `Box`:
```rust
// ⚠️
struct HoldsANumber(u8);
fn main() {
let my_number = HoldsANumber(20);
println!("{}", *my_number + 20);
}
```
The error is:
```text
error[E0614]: type `HoldsANumber` cannot be dereferenced
--> src\main.rs:24:22
|
24 | println!("{:?}", *my_number + 20);
```
We can of course do this: `println!("{:?}", my_number.0 + 20);`. But then we are just adding a separate `u8` to the 20. It would be nice if we could just add them together. The message `cannot be dereferenced` gives us a clue: we need to implement `Deref`. Something simple that implements `Deref` is sometimes called a "smart pointer". It can point to the item, has information about it, and can use its methods. Because right now we can add `my_number.0`, which is a `u8`, but we can't add a `HoldsANumber`: all it has so far is `Debug`.
By the way, `String` is actually a smart pointer to `&str` and `Vec` is a smart pointer to array (or other types). So we have actually been using smart pointers since the beginning.
Implementing `Deref` is not too hard and the examples in the standard library are easy. So we follow them and now our `Deref` looks like this:
```rust
// 🚧
impl Deref for HoldsANumber {
type Target = u8; // Remember, this is the "associated type": the type that goes together.
fn deref(&self) -> &Self::Target { // Rust calls .deref() when you use *. We just defined Target as a u8 so this is easy to understand
&self.0 // We chose &self.0 because it's a tuple struct. In a named struct it would be something like "&self.number"
}
}
```
So now we can do this with `*`:
```rust
use std::ops::Deref;
#[derive(Debug)]
struct HoldsANumber(u8);
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_number = HoldsANumber(20);
println!("{:?}", *my_number + 20);
}
```
So that will print `40` and we didn't need to write `my_number.0`. That means we get the methods of `u8` and we can write our own methods for `HoldsANumber`. We will add our own simple method and use another method we get from `u8` called `.checked_sub()`. That method tries to do subtraction, and if it can't do it, it gives a `None`. Remember, a `u8` can't be negative so it's safer to do `.checked_sub()` so we don't panic.
```rust
use std::ops::Deref;
struct HoldsANumber(u8);
impl HoldsANumber {
fn prints_the_number_times_two(&self) {
println!("{}", self.0 * 2);
}
}
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_number = HoldsANumber(20);
println!("{:?}", my_number.checked_sub(100)); // This method comes from u8
my_number.prints_the_number_times_two(); // This is our own method
}
```
This prints:
```text
40
None
```
We can also implement `DerefMut` so we can change the values through `*`. It looks almost the same. You need `Deref` before you can implement `DerefMut`.
```rust
use std::ops::{Deref, DerefMut};
struct HoldsANumber(u8);
impl HoldsANumber {
fn prints_the_number_times_two(&self) {
println!("{}", self.0 * 2);
}
}
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for HoldsANumber { // You don't need type Target = u8; here because it already knows thanks to Deref
fn deref_mut(&mut self) -> &mut Self::Target { // Everything else is the same except it says mut everywhere
So you can see that `Deref` gives your type a lot of power. This is why the standard library says: `Deref should only be implemented for smart pointers to avoid confusion`. That is because you can do some strange things with `Deref` for a complicated type. Let's imagine a `Character` struct for a game. A new `Character` needs some stats like intelligence and strength. So here is our first character:
Now let's imagine that we want to keep character hit points in a big vec. Maybe we'll put monster data in there too, and keep it all together. Since `hit_points` is an `i8`, we implement `Deref` so we can do all sorts of math on it. But look at how strange it looks in our `main()` function now:
This just prints `[5, 5]`. Our code is now very strange for someone to read. We can read `Deref` just above `main()` and figure out that `*billy` means `i8`, but what if there was a lot of code? Maybe our code is 2000 lines long, and suddenly we have to figure out why we are `.push()`ing `*billy`. `Character` is certainly more than just a smart pointer for `i8`.
Of course, it is not illegal to write `hit_points_vec.push(*billy)`, but it makes the code look very strange. Probably a simple `.get_hp()` method would be much better, or another struct that holds the characters. Then you could iterate through and push the `hit_points` for each one. `Deref` gives a lot of power but it's good to make sure that the code is logical.
Every time you write code in Rust, you are writing it in a `crate`. A `crate` is the file, or files, that go together for your code. Inside the file you write you can also make a `mod`. A `mod` is a space for functions, structs, etc. and is used for a few reasons:
- Building your code: it helps you think about the general structure of your code. This can be important as your code gets larger and larger.
- Reading your code: people can understand your code more easily. For example, when you see `std::collections::HashMap` you know that it's in std inside the module `collections`. This gives you a hint that maybe there are more collection types inside `collections` that you can try.
- Privacy: everything starts out as private. That lets you keep users from using functions directly.
To make a `mod`, just write `mod` and start a code block with `{}`. We will make a mod called `print_things` that has some printing-related functions.
```rust
mod print_things {
use std::fmt::Display;
fn prints_one_thing<T:Display>(input: T) { // Print anything that implements Display
You can see that we wrote `use std::fmt::Display;` inside `print_things`, because it is a separate space. If you wrote `use std::fmt::Display;` inside `main()` it wouldn't help. Also, we can't call it from `main()` right now. Without the `pub` keyword in front of `fn` it will stay private. Let's try to call it without `pub`. Here's one way to write it:
`crate` means inside this file. Inside that is the mod `print_things`, then finally the `prints_one_thing()` function. You can write that every time, or you can write `use` to import it. Now we can see the error that says that it's private:
```rust
// ⚠️
mod print_things {
use std::fmt::Display;
fn prints_one_thing<T:Display>(input: T) {
println!("{}", input)
}
}
fn main() {
use crate::print_things::prints_one_thing;
prints_one_thing(6);
prints_one_thing("Trying to print a string...".to_string());
}
```
Here's the error:
```text
error[E0603]: function `prints_one_thing` is private
--> src\main.rs:10:30
|
10 | use crate::print_things::prints_one_thing;
| ^^^^^^^^^^^^^^^^ private function
|
note: the function `prints_one_thing` is defined here
It's easy to understand that function `print_one_thing` is private. It also shows us where to find the function. This is because once you start using `mod` you might also be using more than one file as well, and it can be hard to find things.
Now we just write `pub fn` instead of `fn` and everything works.
```rust
mod print_things {
use std::fmt::Display;
pub fn prints_one_thing<T:Display>(input: T) {
println!("{}", input)
}
}
fn main() {
use crate::print_things::prints_one_thing;
prints_one_thing(6);
prints_one_thing("Trying to print a string...".to_string());
}
```
This prints:
```text
6
Trying to print a string...
```
How about `pub` for a struct, enum, trait, or module? `pub` works like this for them:
-`pub` for a struct: it makes the struct public, but the items are not public. To make an item public, you have to write `pub` for each one too.
-`pub` for an enum or trait: everything becomes public. This makes sense because traits are about giving the same behaviour to something. And enums are about a selection between items, and you need to see them to select them.
-`pub` for a module: the first module will be `pub` because if it isn't pub then nobody can touch anything in it at all. But modules inside modules need `pub` to be public.
So let's put a struct named `Billy` inside `print_things`. This struct will be almost all public, but not quite. The struct is public so it will say `pub struct Billy`. Inside it will have a `name` and `times_to_print`. `name` will not be public, because we only want the user to create structs named `"Billy".to_string()`. But the user can select the number of times to print, so that will be public. It looks like this:
```rust
mod print_things {
use std::fmt::{Display, Debug};
#[derive(Debug)]
pub struct Billy { // Billy is public
name: String, // but name is private.
pub times_to_print: u32,
}
impl Billy {
pub fn new(times_to_print: u32) -> Self { // That means the user needs to use new to create a Billy. The user can only input times_to_print
Self {
name: "Billy".to_string(), // We choose the name - the user can't
times_to_print,
}
}
pub fn print_billy(&self) { // This function prints a Billy
for _ in 0..self.times_to_print {
println!("{:?}", self.name);
}
}
}
pub fn prints_one_thing<T:Display>(input: T) {
println!("{}", input)
}
}
fn main() {
use crate::print_things::*; // Now we use *. This imports everything from print_things
let my_billy = Billy::new(3);
my_billy.print_billy();
}
```
This will print:
```text
"Billy"
"Billy"
"Billy"
```
By the way, the `*` to import everything is called the "glob operator". Glob means "global", so it means everything.
Inside a `mod` you can create other mods. A child mod (a mod inside of a mod) can always use anything inside a parent mod. You can see this in the next example where we have a `mod city` inside a `mod province` inside a `mod country`. You can think of the structure like this: even if you are in a country, you might not be in a province. And even if you are in a province, you might not be in a city. But if you are in a city, you are in its province and you are its country.
```rust
mod country { // The main mod doesn't need pub
fn print_country(country: &str) { // Note: this function isn't public
println!("We are in the country of {}", country);
}
pub mod province { // Make this mod public
fn print_province(province: &str) { // Note: this function isn't public
println!("in the province of {}", province);
}
pub mod city { // Make this mod public
pub fn print_city(country: &str, province: &str, city: &str) { // This function is public though
crate::country::province::city::print_city("Canada", "New Brunswick", "Moncton");
}
```
The interesting part is that `print_city` can access `print_province` and `print_country`. That's because `mod city` is inside the other mods. It doesn't need `pub` in front of `print_province` to use it. And that makes sense: a city doesn't need to do anything to be inside a province and inside a country.
You probably noticed that `crate::country::province::print_province(province);` is very long. When we are inside a module we can use `super` to bring in items from above. Actually the word super itself means "above", like in "superior". In our example we only used the function once, but if you use it more then it is a good idea to import. It can also be a good idea if it makes your code easier to read, even if you only use the function once. The code is almost the same now, but a bit easier to read:
```rust
mod country {
fn print_country(country: &str) {
println!("We are in the country of {}", country);
}
pub mod province {
fn print_province(province: &str) {
println!("in the province of {}", province);
}
pub mod city {
use super::super::*; // use everything in "above above": that means mod country
use super::*; // use everything in "above": that means mod province
Testing is a good subject to learn now that we understand modules. Testing your code is very easy in Rust, because you can write tests right next to your code.
The easiest way to start testing is to add `#[test]` above a function. Here is a simple one:
But if you try to run it in the Playground, it gives an error: ``error[E0601]: `main` function not found in crate `playground``. That's because you don't use _Run_ for tests, you use _Test_. Also, you don't use a `main()` function for tests - they go outside. To run this in the Playground, click on `···` next to _RUN_ and change it to _Test_. Now if you click on it, it will run the test. Here is the output:
`assert_eq!(left, right)` is the main way to test a function in Rust. If it doesn't work, it will show the different values: left has 2, but right has 3.
What does `RUST_BACKTRACE=1` mean? This is a setting on your computer to give a lot more information about errors. Luckily the Playground has it too: click on `···` next to `STABLE` and set backtrace to `ENABLED`. If you do that, it will give you *a lot* of information:
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/libstd/panicking.rs:410
13: playground::two_is_two
at src/lib.rs:3
14: playground::two_is_two::{{closure}}
at src/lib.rs:2
15: core::ops::function::FnOnce::call_once
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/libcore/ops/function.rs:232
16: <alloc::boxed::Box<F> as core::ops::function::FnOnce<A>>::call_once
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/liballoc/boxed.rs:1076
17: <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/libstd/panic.rs:318
18: std::panicking::try::do_call
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/libstd/panicking.rs:297
19: std::panicking::try
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/libstd/panicking.rs:274
20: std::panic::catch_unwind
at /rustc/c367798cfd3817ca6ae908ce675d1d99242af148/src/libstd/panic.rs:394
21: test::run_test_in_process
at src/libtest/lib.rs:541
22: test::run_test::run_test_inner::{{closure}}
at src/libtest/lib.rs:450
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
failures:
two_is_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
```
You don't need to use a backtrace unless you really can't find where the problem is. But luckily you don't need to understand it all either. If you keep reading, you will eventually see line 13 where it says `playground` - that's where it talks about your code. Everything else is about what Rust is doing in other libraries to run your program. But these two lines show you that it looked at line 2 and line 3 of playground, which is a hint to check there.
```text
13: playground::two_is_two
at src/lib.rs:3
14: playground::two_is_two::{{closure}}
at src/lib.rs:2
```
So let's turn backtrace off again and return to regular tests. Now we'll write some other functions, and use test functions to test them. Here are a few:
```rust
fn return_two() -> i8 {
2
}
#[test]
fn it_returns_two() {
assert_eq!(return_two(), 2);
}
fn return_six() -> i8 {
4 + return_two()
}
#[test]
fn it_returns_six() {
assert_eq!(return_six(), 6)
}
```
Now it runs both:
```text
running 2 tests
test it_returns_two ... ok
test it_returns_six ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
That's not too hard.
Usually you will want to put your tests in their own module. To do this, use the same `mod` keyword and add `#[cfg(test)]` above it (remember: `cfg` means "configure). You also want to continue to write `#[test]` above each test. This is because later on when you install Rust, you can do more complicated testing. You will be able to run one test, or all of them, or run a few. Also don't forget to write `use super::*;` because the test module needs to use the functions above it. Now it will look like this:
```rust
fn return_two() -> i8 {
2
}
fn return_six() -> i8 {
4 + return_two()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_returns_six() {
assert_eq!(return_six(), 6)
}
#[test]
fn it_returns_two() {
assert_eq!(return_two(), 2);
}
}
```
### Test-driven development
You might see the words "test-driven development" when reading about Rust or another programming language. It's one way to write programs, and some people like it while others prefer something else. "Test-driven development" means "writing tests first, then writing the code". When you do this, you will have a lot of tests for everything you want your code to do. Then you start writing the code, and run the tests to see if you did it right. This is pretty easy in Rust because the compiler gives a lot of information about what to fix. Let's write a small example of test-driven development and see what it looks like.
Let's imagine a calculator that takes user input. It can add (+) and it can subtract (-). If the user writes "5 + 6" it should return 11, if the user writes "5 + 6 - 7" it should return 4, and so on. So we'll start with test functions. You can also see that function names in tests are usually quite long. That is because you might run a lot of tests, and you want to understand which tests have failed.
We'll imagine that a single function called `math()` will do everything. It will return an `i32` (we won't use floats). Because it needs to return something, we'll just return `6` every time. Then we will write three test functions. They will all fail, of course. Now the code looks like this:
and all the information about ``thread 'tests::one_plus_one_is_two' panicked at 'assertion failed: `(left == right)``. We don't need to print it all here.
Now to think about how to make the calculator. We will accept any number, and the symbols `+-`. We will allow spaces, but nothing else. So let's start with a `const` that contains all the values. Then we will use `.chars()` to iterate by character, and `.all()` to make sure they are all inside.
The next step is to write the actual calculator. This is the interesting part about test-driven development: the actual writing starts much later. First we will put the logic together for the calculator. We want the following:
- All empty spaces should be removed. This is easy with `.filter()`
- The input should turn into a `Vec` with all the inputs. `+` doesn't need to be an input, but when the program sees `+` it should know that the number is done. For example, the input `11+1` should do something like this: 1) See `1`, push it into an empty string. 2) See another 1, push it into the string (it is now "11"). 3) See a `+`, know the number has ended. It will push the string into the vec, then clear the string.
- The program must count the number of `-`. An odd number (1, 3, 5...) will mean subtract, an even number (2, 4, 6...) will mean add. So "1--9" should give 10, not -8.
- The program should remove anything after the last number. `5+5+++++----` is made out of all the characters in `OKAY_CHARACTERS`, but it should turn to `5+5`. This is easy with `.trim_end_matches()`, where you remove anything that matches at the end of a `&str`.
(By the way, `.trim_end_matches()` and `.trim_start_matches()` used to be `trim_right_matches()` and `trim_left_matches()`. But then people noticed that some languages write from right to left (Persian, Hebrew, Arabic, etc.) so right and left would be wrong. You might still see the older names in some code but they are the same thing.)
First we just want to pass all the tests. After we pass the tests, we can "refactor". Refactor means to make the code better, usually through things like structs and enums and methods. Here is our code to make the tests pass:
```rust
const OKAY_CHARACTERS: &str = "1234567890+- ";
fn math(input: &str) -> i32 {
if let false = input.chars().all(|character| OKAY_CHARACTERS.contains(character)) {
panic!("Please only input numbers, +-, or spaces");
}
let input = input.trim_end_matches(|x| "+-".contains(x)).chars().filter(|x| *x != ' ').collect::<String>(); // Remove + and - at the end, and all spaces
let mut result_vec = vec![]; // Results go in here
let mut push_string = String::new(); // This is the string we push in every time. We will keep reusing it in the loop.
for character in input.chars() {
match character {
'+' => {
if !push_string.is_empty() { // If the string is empty, we don't want to push "" into result_vec
result_vec.push(push_string.clone()); // But if it's not empty, it will be a number. Push it into the vec
push_string.clear(); // Then clear the string
}
},
'-' => { // If we get a -,
if push_string.contains('-') || push_string.is_empty() { // check to see if it's empty or has a -
push_string.push(character) // if so, then push it in
} else { // otherwise, it will contain a number
result_vec.push(push_string.clone()); // so push the number into result_vec, clear it and then push -
push_string.clear();
push_string.push(character);
}
},
number => { // number here means "anything else that matches". We selected the name here
if push_string.contains('-') { // We might have some - characters to push in first
result_vec.push(push_string.clone());
push_string.clear();
push_string.push(number);
} else { // But if we don't, that means we can push the number in
push_string.push(number);
}
},
}
}
result_vec.push(push_string); // Push one last time after the loop is over. Don't need to .clone() because we don't use it anymore
let mut total = 0; // Now it's time to do math. Start with a total
assert_eq!(math("8 - 9 +9-----+++++"), 8); // This is a new test
}
#[test]
#[should_panic]
fn panics_when_characters_not_right() {
math("7 + seven");
}
}
```
And now the tests pass!
```text
running 6 tests
test tests::one_minus_minus_one_is_two ... ok
test tests::nine_plus_nine_minus_nine_minus_nine_is_zero ... ok
test tests::one_minus_two_is_minus_one ... ok
test tests::eight_minus_nine_plus_nine_is_eight_even_with_characters_on_the_end ... ok
test tests::one_plus_one_is_two ... ok
test tests::panics_when_characters_not_right ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
You can see that there is a back and forth process in test-driven development. It's something like this:
- First you write all the tests you can think of.
- Then you start writing the code.
- As you write the code, you get ideas for other tests.
- You add the tests, and your tests grow as you go. The more tests you have, the more times your code gets checked.
Of course, tests don't check everything and it is wrong to think that "passing all tests = the code is pefect". But tests are great for when you change your code. If you change your code later on and run the tests, if one of them doesn't work you will know what to fix.
Now we can rewrite (refactor) the code a bit. One good way to start is with clippy. If you installed Rust then you can type `cargo clippy`, and if you're using the Playground then click on `TOOLS` and select Clippy. Clippy will look at your code and give you tips to make it simpler. Our code doesn't have any mistakes, but it could be better.
Clippy tells us two things:
```text
warning: this loop could be written as a `for` loop
--> src/lib.rs:44:5
|
44 | while let Some(entry) = math_iter.next() { // Iter through the items
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for entry in math_iter`
|
= note: `#[warn(clippy::while_let_on_iterator)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator
warning: equality checks against true are unnecessary
--> src/lib.rs:53:12
|
53 | if adds == true {
| ^^^^^^^^^^^^ help: try simplifying it as shown: `adds`
|
= note: `#[warn(clippy::bool_comparison)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
```
This is true: `for entry in math_iter` is much simpler than `while let Some(entry) = math_iter.next()`. And a `for` loop is actually an iterator so we don't have any reason to write `.iter()`. Thanks, clippy! And also we didn't need to make `math_iter`: we can just write `for entry in result_vec`.
And the second point is true too: `if adds == true` can just be `if adds` (because `adds` = `true`).
Now we'll start some real refactoring. Instead of separate variables, we will create a `Calculator` struct. This will have all the variables we used together. We will change two names to make it more clear: `result_vec` will become `results`, and `push_string` will become `current_input` (current means "now"). And so far it only has one method: new.
```rust
// 🚧
#[derive(Clone)]
struct Calculator {
results: Vec<String>,
current_input: String,
total: i32,
adds: bool,
}
impl Calculator {
fn new() -> Self {
Self {
results: vec![],
current_input: String::new(),
total: 0,
adds: true,
}
}
}
```
Now our code is actually a bit longer, but easier to read. For example, `if adds` is now `if calculator.adds`, which is exactly like reading English. It looks like this:
```rust
#[derive(Clone)]
struct Calculator {
results: Vec<String>,
current_input: String,
total: i32,
adds: bool,
}
impl Calculator {
fn new() -> Self {
Self {
results: vec![],
current_input: String::new(),
total: 0,
adds: true,
}
}
}
const OKAY_CHARACTERS: &str = "1234567890+- ";
fn math(input: &str) -> i32 {
if let false = input.chars().all(|character| OKAY_CHARACTERS.contains(character)) {
panic!("Please only input numbers, +-, or spaces");
}
let input = input.trim_end_matches(|x| "+-".contains(x)).chars().filter(|x| *x != ' ').collect::<String>();
Finally we add two new methods. One is called `.clear()` and clears the `current_input()`. The other one is called `push_char()` and pushes the input onto `current_input()`. Here is our refactored code:
```rust
#[derive(Clone)]
struct Calculator {
results: Vec<String>,
current_input: String,
total: i32,
adds: bool,
}
impl Calculator {
fn new() -> Self {
Self {
results: vec![],
current_input: String::new(),
total: 0,
adds: true,
}
}
fn clear(&mut self) {
self.current_input.clear();
}
fn push_char(&mut self, character: char) {
self.current_input.push(character);
}
}
const OKAY_CHARACTERS: &str = "1234567890+- ";
fn math(input: &str) -> i32 {
if let false = input.chars().all(|character| OKAY_CHARACTERS.contains(character)) {
panic!("Please only input numbers, +-, or spaces");
}
let input = input.trim_end_matches(|x| "+-".contains(x)).chars().filter(|x| *x != ' ').collect::<String>();
This is probably good enough for now. We could write more methods but lines like `calculator.results.push(calculator.current_input.clone());` are already very clear. Refactoring is best when you can still read the code well after you are done. You don't want to just refactor to make the code short: `calc.clr()` is much worse than `calculator.clear()`, for example.
For this section you *almost* need to install Rust, but we can still use just the Playground. Now we are going to learn how to import crates that other people have written. This is important in Rust because of two reasons:
- It is very easy to import other crates, and
- The Rust standard library is quite small.
That means that it is normal in Rust to bring in an external crate for a lot of basic functions. The idea is that if it is easy to use external crates, then you can choose the best one. Maybe one person will make a crate for one function, and then someone else will make a better one.
To begin learning external crates, we will start with the most common one: `rand`.
### rand
Did you notice that we didn't use any random numbers yet? That's because random numbers aren't in the standard library. But there are a lot of crates that are "almost standard library" crates because everybody uses them. In any case, it's very easy to bring in a crate. If you have Rust on your computer, there is a file called `cargo.toml` that has this information. A `cargo.toml` file looks like this when you start:
```text
[package]
name = "rust_book"
version = "0.1.0"
authors = ["David MacLeod"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
```
Now if you want to add the `rand` crate, search for it on `crates.io`, which is where all the crates go. That takes you to `https://crates.io/crates/rand`. And when you click on that, you can see a screen that says `Cargo.toml rand = "0.7.3"`. All you do is add that under [dependencies] like this:
```text
[package]
name = "rust_book"
version = "0.1.0"
authors = ["David MacLeod"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.7.3"
```
And then Cargo will do the rest for you. Then you can start writing code like [this example code](https://docs.rs/rand/0.7.3/rand/) on the `rand` document website. To get to the documents you can click on the `docs` button in [the page on crates.io](https://crates.io/crates/rand).
So that's enough about Cargo: we are still using just the Playground. Luckily, the Playground already has the top 100 crates installed. So you don't need to write in `cargo.toml` yet. On the Playground you can imagine that it has a long list like this with 100 crates:
```text
[dependencies]
rand = "0.7.3"
some_other_crate = "0.1.0"
another_nice_crate = "1.7"
```
That means that to use `rand`, you can just do this.
```rust
use rand; // This means the whole crate rand
// On your computer you can't just write this;
// you need to write in the cargo.toml file first
fn main() {
for _ in 0..5 {
let random_u16 = rand::random::<u16>();
print!("{} ", random_u16);
}
}
```
It will print a different `u16` number every time, like `42266 52873 56528 46927 6867`.
The main functions in `rand` are `random` and `thread_rng` (rng means "random number generator"). And actually if you look at `random` it says: "This is simply a shortcut for `thread_rng().gen()`". So it's actually just `thread_rng` that does almost everything.
Here is a simple example of numbers from 1 to 10. To get those numbers, we use `.gen_range()` between 1 and 11.
```rust
use rand::{thread_rng, Rng}; // Or just use rand::*; if we are lazy
fn main() {
let mut number_maker = thread_rng();
for _ in 0..5 {
print!("{} ", number_maker.gen_range(1, 11));
}
}
```
This will print something like `7 2 4 8 6`.
With random numbers we can do fun things like make characters for a game. We will use `rand` and some other things we know to make them. In this game our characters have six stats, and you use a d6 for them. A d6 is a cube that gives 1, 2, 3, 4, 5, or 6 when you throw it. Each character rolls a d6 three times, so each stat is between 3 and 18.
But sometimes it can be unfair if your character has something low like a 3 or 4. If your strength is 3 you can't carry anything, for example. So there is one more method that uses a d6 four times. You roll it four times, and throw away the lowest number. So if you roll 3, 3, 1, 6 then you keep 3, 3, 6 = 12. We will make this method too so the owner of the game can decide.
Here is our simple character creator. We created a `Character` struct for the stats, and even implemented `Display` to print it the way we want.
```rust
use rand::{thread_rng, Rng}; // Or just use rand::*; if we are lazy
use std::fmt; // Going to impl Display for our character
struct Character {
strength: u8,
dexterity: u8, // This means "body quickness"
constitution: u8, // This means "health"
intelligence: u8,
wisdom: u8,
charisma: u8, // This means "popularity with people"
}
fn three_die_six() -> u8 { // A "die" is the thing you throw to get the number
let mut generator = thread_rng(); // Create our random number generator
let mut stat = 0; // This is the total
for _ in 0..3 {
stat += generator.gen_range(1, 7); // Add each time
}
stat // Return the total
}
fn four_die_six() -> u8 {
let mut generator = thread_rng();
let mut results = vec![]; // First put the numbers in a vec
for _ in 0..4 {
results.push(generator.gen_range(1, 7));
}
results.sort(); // Now a result like [4, 3, 2, 6] becomes [2, 3, 4, 6]
results.remove(0); // Now it would be [3, 4, 6]
results.iter().sum() // Return this result
}
impl Character {
fn new(three_dice: bool) -> Self { // true for three dice, false for four
match three_dice {
true => Self {
strength: three_die_six(),
dexterity: three_die_six(),
constitution: three_die_six(),
intelligence: three_die_six(),
wisdom: three_die_six(),
charisma: three_die_six(),
},
false => Self {
strength: four_die_six(),
dexterity: four_die_six(),
constitution: four_die_six(),
intelligence: four_die_six(),
wisdom: four_die_six(),
charisma: four_die_six(),
},
}
}
fn display(&self) { // We can do this because we implemented Display below
println!("{}", self);
println!();
}
}
impl fmt::Display for Character { // Just follow the code for in https://doc.rust-lang.org/std/fmt/trait.Display.html and change it a bit
`rayon` is a popular crate that lets you speed up your Rust code. It's popular because it creates threads without needing to write things like `thread::spawn`. In other words, it is popular because it is effective but easy to write. For example:
-`.iter()`, `.iter_mut()`, `into_iter()` in rayon is written like this:
-`.par_iter()`, `.par_iter_mut()`, `par_into_iter()`. So you just add `par_` and your code becomes much faster. (par means "parallel")
Other methods are the same: `.chars()` is `.par_chars()`, and so on.
Here is an example of a simple piece of code that is making the computer do a lot of work:
It creates a vector with 200,000 items: each one is 0. Then it calls `.enumerate()` to get the index for each number, and changes the 0 to the index number. It's too long to print so we only print items 5000 to 5004. This is still very fast in Rust, but if you want you can make it faster with Rayon. The code is almost the same:
```rust
use rayon::prelude::*; // Import rayon
fn main() {
let mut my_vec = vec![0; 200_000];
my_vec.par_iter_mut().enumerate().for_each(|(index, number)| *number+=index+1); // add par_ to iter_mut
println!("{:?}", &my_vec[5000..5005]);
}
```
And that's it. `rayon` has many other methods to customize what you want to do, but at its most simple it is just "add `_par` to make your program faster".
You saw that we can learn almost anything in Rust just using the Playground. But if you learned everything so far, you will probably want Rust on your computer now. There are always some things that you can't do with the Playground like opening files or writing Rust in more than one just file. Some other things you need Rust on your computer for are input and flags. But most important is that with Rust on your computer you can use crates. We already learned about crates, but in the Playground you could only use the most popular ones. But with Rust on youn computer you can use any crate in your program.