This note includes a quick set of notes I took while learning [[Rust]] using [The Rust Programming Language](https://doc.rust-lang.org/book/), a free book to get started on the language. --- ## Chapter 1 — Getting Started ### Installation Installing Rust is really simple: ```bash curl --proto '=https' --tlsv1.2 -sSf <https://sh.rustup.rs> | sh ``` After running this command and making sure `$HOME/.cargo/bin` is in the `$PATH`, all the utilities should be ready to use. These utilities are able to update themselves, using: ```bash # Update the toolkit $ rustup update # Uninstall $ rustup self uninstall ``` It is also possible to check the Rust documentation offline by running `rustup doc`. ### Hello, World! Now that everything is ready, we can write our first "Hello, world!" program. The code for this example and the rest of the book is available in the local project `learn-rust`. The program itself is as straightforward as it can be: ```rust fn main() { println!("Hello, world!"); } ``` And the compilation is as well pretty straightforward: ```bash $ rustc main.rs $ ./main Hello, world! ``` ### Hello, Cargo! The next section expands the previous program to be developed as a Cargo crate. Cargo is Rust's build system and package manager. To create a project with Cargo: ```bash $ cargo new hello_cargo ``` What has Cargo exactly done? 1. It has created a git repository and an adequate gitignore. 2. It has added a `Cargo.toml` configuration file with sensible defaults. 3. It has created an example `src/main.rs` To build and run a Cargo project: ```bash # To create and run a debug version (with no code optimizations) $ cargo build Compiling hello-cargo v0.1.0 (/home/dvicente/dev/learn-rust/hello-cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.19 $ ./target/debug/hello_cargo Hello, world! # To create and run a release version $ cargo build --release Compiling hello-cargo v0.1.0 (/home/dvicente/dev/learn-rust/hello-cargo) Finished release [optimized] target(s) in 0.17s $ ./target/release/hello_cargo Hello, world! ``` When building for the first time, Cargo creates a `Cargo.lock` file that locks the dependency versions. --- ## Chapter 2 — Programming a Guessing Game The chapter is pretty straightforward and the code can be easily followed even if you don't know Rust. The code is also in the repository, inside the crate `guessing_game`. --- ## Chapter 3 — Common Programming Concepts ### Variables As a language, one of Rust's main objectives is to provide safety, and there are several ways it tries to achieve it. In Rust, **variables are immutable by default**. This forces you to use `mut` keyword to explicitly state when a variable can be mutated. The difference with constants is that constants (declared with the word `const` instead of `let`) are that the latter must be declared with an explicit data type and can be declared in any scope. Even if they are immutable, variables can be shadowed by using the `let` keyword again, which allows to even change the data type of the variable. ### Datatypes Rust is strongly and statically typed, so all data types must be known at compile time. Regular datatypes are what you could expect (numeric, Unicode-based characters, etc). This notes will focus mainly in the compound types. The **tuple type** is a way of grouping together a fixed number of values with a variety of types into one type. It can also be deconstructed (similar to Python's unpacking). ```rust let tup: (i32, f64, u8) = (500, 6.4, 1); let (x, y, z) = tup; ``` An **array** is a way of grouping together a _fixed_ number of values of a single data type. It is allocated on the stack. The length of the array can be declared differently in the code, as per the following snippet. ```rust let xs: [i32; 5] = [1, 2, 3, 4, 5]; ``` This syntax is similar to the repetition one: in order to create an array of length 5 where all the elements are 3, you could use: ```rust let xs = [3; 5]; ``` Elements of an array can be accessed using the squared brackets notation and the first element is `0`. An out-of-bounds access will result in a _runtime_ error. ### Functions This is pretty straightforward and an example should be enough: ```rust fn plus_one(x: i32) -> i32 { x + 1 } ``` ### Control flow There are several ways to control the execution of a Rust program: ```rust // if-else statement if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 3, or 2"); } // let-if statement let number = if condition { 5 } else { 6 }; // simplest loop -- can use continue or break loop { println!("forever!"); } // Simple while loop while number != 0 { println!("{}!", number); number -= 1; } // for loops are based on for-each let a = [1, 2, 3, 4, 5]; // works like `(1..5)` for element in a.iter() { println!("the value is: {}", element); } ``` --- ## Chapter 4 — Understanding Ownership To understand ownership, we first must clarify once again what the stack and the heap are. Both are parts of memory used to store code at runtime, but with structural differences: - The **stack** is structured as _last in, first out_ (LIFO) and only variables whose _size is fixed and known at compile_ time are stored in the stack. - The **heap** is where the variables with _unknown size_ are stored. Space in the heap must be requested (allocating) and it is referred to with a pointer (its address). By this nature, using the stack is always faster since no allocation is needed — there is always the necessary size at the top of the stack. Also, no pointers are needed — the data needed is always on top of the stack. **Ownership is a mechanisms to organize the data on the heap**. ### Ownership rules There are three rules to take into account regarding ownership: 1. Each value in Rust has a variable that's called its **owner**. 2. There can only be **one owner at a time**. 3. When the **owner goes out of scope**, the value is **destroyed**. ### References and Borrowing References (`&`) are the mechanism used to manipulate values when the scope changes and we do not wish them to be dropped. ```rust fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() } ``` This mechanism to passing references without changing ownership is, adequately, called **borrowing**. In the example above, `s` cannot be mutated in the function — it does not own it. To fix this is possible to use **mutable references**, which allow the function to modify the values which the original variable points to. ```rust fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); } ``` There is one important restriction: you can only have **one mutable reference to a particular piece of data in a particular scope**. A corollary to this is that no regular references can be created in a scope when a mutable reference exists. This mechanism is created to prevent data races. Note that a reference scope starts from where it is introduced until its last time it is used. Therefore, this is valid code: ```rust let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem println!("{} and {}", r1, r2); // r1 and r2 are no longer used after this point let r3 = &mut s; // no problem println!("{}", r3); ``` The chapter ends with a note about dangling references and how to prevent them in Rust (just don't return dangling references, basically). ### Slices In Rust, slices are references to a part of a collection. The notation is similar to that of range's, as can be seen in the example below: ```rust let s = String::from("hello world"); // the type of a string slice is `&str` let hello = &s[0..5]; let world = &s[6..11]; ``` The type of a string slice is `&str` because string literals are actually slices! They point to a certain value inside the binary itself. When using string slices as parameters, it is usually more flexible to always use `&str` instead of `&String`: ```rust // assuming a function `fn first_word(s: &str) -> &str` fn main() { let my_string = String::from("hello world"); // first_word works on slices of `String`s let word = first_word(&my_string[..]); let my_string_literal = "hello world"; // first_word works on slices of string literals let word = first_word(&my_string_literal[..]); // Because string literals *are* string slices already, // this works too, without the slice syntax! let word = first_word(my_string_literal); } ``` Slices can also be applied to other collections! For example, this code is valid, and `slice` is of type `&[i32]`: ```rust let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; ``` --- ## Chapter 5 — Using Structs to Structure Related Data A struct is a custom data type that lets you name and package together multiple related values that make up a meaningful group, for example: ```rust struct User { username: String, email: String, sign_in_count: u64, active: bool, } ``` Initializing a struct is pretty straightforward: ```rust let user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; ``` A struct must be declared as mutable in order to change any of their fields. The whole instance is mutable in that case. It is convenient to use the _field init shorthand_ when the name of a field is the same as a variable: ```rust fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } } ``` There is another shorthand calle _struct update syntax_, that allows to update certain values of another instance and reuse the rest when creating a new one: ```rust let user2 = User { email: String::from("[email protected]"), username: String::from("anotherusername567"), ..user1 }; ``` There is a simple version of the structs called _tuple structs_, which do not have named fields: ```rust struct Color(i32, i32, i32); struct Point(i32, i32, i32); let black = Color(0, 0, 0); let origin = Point(0, 0, 0); ``` ### Method syntax You can define methods for the structs. The definition of the methods are, interestingly enough, not coupled with the actual struct's definition: instead, they are added in `impl` blocks: ```rust #[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } } ``` The first one is a method, which can be called on an instance using `rect.area()`. The latter is an associated function and does not take an instance as a parameter, it can be called using `Rectangle::square(3)`. --- ## Chapter 6 — Enums & Pattern Matching In Rust, enums are closer to algebraic data types than regular C enums. The definition is straightforward enough: ```rust // Simpler way to define an enum enum IpAddrKind { V4, V6, } let four = IpAddrKind::V4; let six = IpAddrKind::V6; // Enum with data in the variants enum IpAddr { V4(String), V6(String), } let home = IpAddr::V4(String::from("127.0.0.1")); let loopback = IpAddr::V6(String::from("::1")); ``` To unpack the values inside the enum types, probably the most useful feature is pattern matching using `match`: ```rust fn connect_to(ip: IpAddr) -> bool { match ip { IpAddr::V4(addr) => connect_ip4(addr), IpAddr::V6(addr) => { println!("Using IPv6..."); connect_ip6(addr) } } } ``` `match` is exhaustive, so all the possibilities must be covered. To achieve it, the special pattern `_` can be used as a wildcard and last arm. The last control mechanism to take into account is the `if let` construct, which allows a simple and concise way to perform a single deconstruction. ```rust let ip = IpAddr::V6("::1"); if let IpAddr::V4(addr) = ip { println!("{} is an IPv4 address"); } else { println!("Not an IPv4"); } ``` --- ## Chapter 7 — Managing Growing Projects with Packages, Crates and Modules Rust has a number of features that allow you to manage your code’s organization, including which details are exposed, which details are private, and what names are in each scope in your programs. These features, sometimes collectively referred to as the _module system_, include: - **Packages:** A Cargo feature that lets you build, test, and share crates - **Crates:** A tree of modules that produces a library or executable - **Modules** and **use:** Let you control the organization, scope, and privacy of paths - **Paths:** A way of naming an item, such as a struct, function, or module ### Packages and Crates A **crate** is a binary or library; whose root is a source file hat the Rust compiler starts from; it also makes up the root module of a crate. A **package** is one or more crates that provide a set of functionality; it is defined in its `Cargo.toml` how to build those crates. ### Defining Modules to Control Scope and Privacy **Modules** let us organize code within a crate in froups for readability and easy reuse. Modules also control the _privacy_ of items: whether an item can be used by outside code (public) or is an internal implementation detail and not available for outside use (private). ### Paths for Referring to an Item in the Module Tree There are two different ways to refer to an item: - An **absolute path** starts from a crate root by using a crate name or a literal `crate`. - A relative path starts from the current module and uses `self`, `super` or an identifier in the current module. ```rust mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist(); } // Different crate --- fn serve_order() {} mod back_of_house { fn fix_incorrect_order() { cook_order(); // Relative path using super super::serve_order(); } fn cook_order() {} } ``` --- ## Chapter 8 — Common Collections ### Vectors The first collection type that we can take a look at is `Vec<T>`. Creating a new vector can be done two different ways: ```rust // Creating an empty vector let v1: Vec<i32> = Vec::new(); v1.push(1); v1.push(2); v1.push(3); // Creating a vector with initial values let v2: = vec![1, 2, 3]; ``` Reading elements of vectors use familiar syntaxes for the most part: ```rust let v = vec![1, 2, 3, 4, 5]; // Regular way to access elements returns a reference let third: &i32 = &v[2]; println("The third element is {}", third); // Using v.get returns an Option<&T> match v.get(2) { Some(third) => println!("The third element is {}", third), None => println!("There is no third element."), } ``` Accessing a reference of an element that does not exist will cause the code to panic, and all references created before mutating the vector are invalidated after the mutable borrow. Iterating over the vector can be done using a simple `for` loop: ```rust let v = vec![1, 2, 3]; // immutable iteration for i in &v{ println!("{}", i); } // mutable iteration for i in &mut v{ *i += 50; } ``` It is also an useful technique to use pre-defined enums to store multiple types in a vector: ```rust enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ]; ``` If you don't know the exhaustive set of types to be defined at compile time, this won't work. In that case, maybe a trait object is a better choice. ### Strings Rust has only one string type in the core language, which is the string slice `str` that is usually seen in its borrowed form (`&str`). String slices are references to some UTF-8 encoded string data stored elsewhere, for instance string literals, which are stored in the program's binary. On the other hand, the `String` type is provided by Rust's standard library (rather than coded into the language) and is a growable, mutable, owned, UTF-8 encoded string type. When referring to "strings", we usually mean `String` and `&str` types. The basic operations are straightforward: ```rust // Empty string let mut s = String::new(); // String from literal let s = String::from("initial contents"); // String from a str variable let data = "initial contents"; let s = data.to_string(); // Updating a String let mut s = String::from("foo"); s.push_str("bar"); s.push('!'); let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // Note that s1 has been moved ``` It is important to understand that the Rust compiler is able to coerce the types `&String` and `&str` (as it is happening in the last line). This case is a _deref coercion_, and the compiler is able to turn `&s2` into `&s2[..]`. However, there is a better way to concatenate very complex strings: ```rust let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = format!("{}-{}-{}", s1, s2, s3); ``` Since both `str` and `String` are UTF-8 encoded, Rust does not support string indexing to prevent undesired behavior. Rust actually needs you to be more specific regarding what you want to access: - You can access `s.chars()` to iterate the characters of a string. - You can access `s.bytes()` to iterate the bytes that form the string.