Parsia-Clone

'Documentation is a love letter that you write to your future self.' - Damian Conway

52 minute read - Development

Rust

Github Link

Learning Rust

Install Rust in WSL (from https://www.rust-lang.org/tools/install):

  • curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Chapter 1

cargo whatever
# create a package in `whatever` directory under the current path.
`whatever/src/main.rs` will have `hello world`.

cd whatever

cargo check     # check if it compiles without building

cargo building  # build the package

cargo run       # run

Chapter 2

Every variable is immutable, have to make it mutable with mut: let mut something = 1;

References passed to functions can also be mutable or not regardless of the underlying variable. E.g., we can create an immutable reference to a mutable variable. In the code below read_line needs a mut string, so we pass &mut guess instead of just &guess.

read_line appends whatever is read from the command line to the argument. It does not overwrite anything. Here, it doesn't matter because guess is empty.

use std::io;

fn main() {
    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

expect: The result of read_line is an enum of type io::Result. It can be Ok or Err. If the value is Err then expect is called that crashes the program and displays the message passed to it (Failed to). If the value is Ok then expect will return the number of bytes read which we are not using here. We can use it like this:

let bytes = io::stdin()
    // Read from input and append to guess.
    .read_line(&mut guess)
    .expect("Failed to read line");

println!("Read {} bytes", bytes);

Finally, we have the placeholder similar to printf(%v) in Go.

Dependencies

Edit Cargo.toml and add dependencies like rand = "0.8.3". Then cargo build.

cargo update ignores Cargo.lock and grabs the latest versions of libraries (that fit the versions specified in Cargo.toml).

Now, we can use rand here like this:

use rand::Rng;

fn main() {
    println!("Guess the number!");

    // Generate a "random" number between 1 and 100.
    // Alternative param to "1..101" is "1..=100".
    let secret_number = rand::thread_rng().gen_range(1..101);
    // removed
}

1..101 range == [1, 101) == number between 1 and 100 == 1..=100.

The VS Code Rust language server shows us the docs for methods (and does other things like autocomplete), but it's also possible to see the crate docs with crate doc --open. This will build the docs for each of the dependencies and open them in the browser.

To do a comparison, we add std::cmp::Ordering which is another enum with three values: Less/Greater/Equal.

// ch02/guessing_game/src/main.rs
use std::cmp::Ordering;

fn main() {
    // removed

    // Create a mutable string.
    let mut guess = String::new();

    let bytes = io::stdin()
        // Read from input and append to guess.
        .read_line(&mut guess)
        .expect("Failed to read line");
    
    // cmp compares two values
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
    // removed
}

cmp compares two values. Usable on anything that can be compared. The result is an enum of Ordering. We can use the match to do something based on what was returned. In this case we have three arms (choices). Each arm has a pattern (e.g., Ordering::Less) and an action println!.

This won't compile because secret_number is of type integer and guess is a string so we cannot compare them.

let guess: u32 = guess.trim().parse().expect("Please type a number!");

Instead of creating a new variable to store guess converted to an integer, we can shadow the previous value. This allows us to reuse the variable name and usually used in situations like this.

guess.trim() removes whitespace before/after the string. The string has the new line character(s) because we pressed enter to finish it so it must be trimmed.

.parse() converts a string into a number. To tell Rust which kind of number, we annotate the variable with let guess: u32.

.expect returns the error if parse returns an error and the converted number if the return value is Ok.

Adding a Loop

We can do an infinite loop with

loop {
    println!("Please input your guess.");

    // removed

    println!("You guessed: {}", guess);
}

We can break out of it with break. We want to leave the loop when we guess the number correctly so we add to the actions for the Ordering::Equal arm.

match guess.cmp(&secret_number) {
    Ordering::Less => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal => {
        println!("You win!");
        break;
    }
}

Handling Invalid Input

Entering a non-number will crash the program. Let's handle it. Instead of calling expect we match on the return value of parse.

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,     // If parse worked correctly, store the number in guess.
    Err(_) => continue, // If parse returned an error, go back to the beginning of the loop.
};

If parse worked correctly it returns an Ok value that contains the number. Otherwise, it returns an Err with the error message. In Err(_) we are catching every message (with _).

Chapter 3

Some programming concepts in Rust.

Mutability

We already know this. Every variable is immutable unless we use mut. If we want to modify an immutable variable, the compiler warns us and does not compile the program. See ch03/variables/src/main.rs.

Constants

Created with const keyword. We cannot use mut with them because they are always immutable. Can only be set to a constant expression not something that is calculated at runtime.

const FIRST_NAME = "Parsia";
const TIME: u32 = 10 * 20 * 30;

Naming convention: All uppercase with underscore between words.

Shadowing

Declare a variable with the same name. Interestingly, this can be done in specific scopes. E.g., we can shadow a variable inside a block but it will not be modified outside.

// ch03/shadowing/src/main.rs
fn main() {
    let x = 5;
    
    let x = x + 1; // x = 6
    // Seems like we can just create random blocks here.
    {
        let x = x * 2;  // x = 12
        // x is only shadowed in this scope.
        println!("The value of x in the inner scope is: {}", x);
    }
    // x = 6 because this one is not touched.
    println!("The value of x is: {}", x);
}

Seems like we can just make blocks with { }.

Differences with mut:

  1. We can shadow an immutable variable and create an immutable variable with the same name.
  2. We can change the type and reuse the name. We cannot change the type of a mutable variable.

This works because we are shadowing spaces and creating a new variable of type int.

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
}

We can also make the new spaces mutable:

// ch03/shadowing2/src/main.rs
fn main() {
    let spaces = "   ";
    println!("spaces: {}", spaces);
    let mut spaces = spaces.len();
    println!("spaces: {}", spaces);
    spaces = 1234;
    println!("spaces: {}", spaces);
}

returns:

spaces: [prints three spaces]
spaces: 3
spaces: 1234

If we make a variable mutable but do not modify it, the compiler will give us a warning saying it should not be mutable.

fn main() {
    let spaces = "   ";
    println!("spaces: {}", spaces);
    let mut spaces = spaces.len();
    println!("spaces: {}", spaces);
}

The shadowing spaces (2nd one) is mutable but not modified so we get:

Compiling playground v0.0.1 (/playground)
warning: variable does not need to be mutable
 --> src/main.rs:4:9
  |
4 |     let mut spaces = spaces.len();
  |         ----^^^^^^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` on by default

warning: `playground` (bin "playground") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 1.79s
     Running `target/debug/playground`
Standard Output
spaces:    
spaces: 3

Integers

Like we have seen before. We can have 8, 16, 32, 64, and 128-bit integers. Default is i32.

  • Signed: i8 i16 i32 i64 i128
  • Unsigned: u8 u16 u32 u64 u128

There's also isize (signed) and usize (unsigned) which are based on the machine. E.g., i64 for 64-bit machines.

When writing integer literals we can use some help:

  • _ is ignored so 1_200 and 1200 are equal.
  • Start
    • Hex number with 0x: 0xAB.
    • Octal number with 0o (zero and the letter o): 0o77.
    • Binary number with 0b: 0b0011_1111. See the _ for better readability.
    • Byte with b (only u8): b'A'.

Floating Point

f32 and f64 (default). To specify f32 we need to annotate it:

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

Numeric Operations

As you can imagine.

// ch03/numeric_operations.rs
fn main() {
    // addition
    let sum = 5 + 10;
    println!("5 + 10 = {}", sum); // 5 + 10 = 15

    // subtraction
    let difference = 95.5 - 4.3;
    println!("95.5 - 4.3 = {}", difference); // 95.5 - 4.3 = 91.2

    // multiplication
    let product = 4 * 30;
    println!("4 * 30 = {}", product); // 4 * 30 = 120

    // division
    let quotient = 56.7 / 32.2;
    let floored = 2 / 3; // Results in 0
    println!("56.7 / 32.2 = {}", quotient); // 56.7 / 32.2 = 1.7608695652173911
    println!("2 / 3 = {}", floored); // 2 / 3 = 0

    // remainder
    let remainder = 43 % 5;
    println!("43 % 5 = {}", remainder); // 43 % 5 = 3
}

Boolean

true and false.

Character Type

char: 4-bytes Unicode Scalar values. E.g., U+0031 or emojis.

Used with ' (strings use ").

Tuple

Fixed-size array/slice of values with different types.

// ch03/tuple.rs
fn main() {
    // create a tuple like this, note the type annotation.
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // destructure it to get the values.
    let (x, y, z) = tup;
    println!("x = {}, y = {}, z = {}", x, y, z);
    // x = 500, y = 6.4, z = 1

    // can also get the value by `var.index`.
    println!("tup.0 = {}, tup.1 = {}, tup.2 = {}", tup.0, tup.1, tup.2); 
    // tup.0 = 500, tup.1 = 6.4, tup.2 = 1
}

Array

Fixed-size array of values with the same type. Goes on stack instead of heap.

// ch03/arrays.rs
fn main() {
    let arr = [1, 2, 3];
    println!("arr = {:?}", arr);
    let strings = ["one", "two", "three"];
    println!("strings = {:?}", strings);

    // we can also specify the type and size. See below why we are using &str here.
    let arr2: [&str; 3] = ["one", "two", "three"];
    println!("arr2 = {:?}", arr2);
}

Specify one initial value for all elements:

// these are the same
let a = [3; 5];
let b = [3, 3, 3, 3, 3];

Access array elements like most other languages:

let arr = [1, 2, 3];
let b = arr[0]; // b = 1;

It's possible to access elements beyond the capacity. If the value is known when compiling, the Rust compiler will give us an error:

fn main() {
    
    let arr2: [&str; 3] = ["one", "two", "three"];
    println!("arr2 = {:?}", arr2);
    
    println!("arr2[3] = {}", arr2[3]);
                         //  ^^^^^^^ index out of bounds: the length is 3 but the index is 3
    
    let c = 1 + 2;
    println!("arr2[c] = {}", arr2[c]);
                         //  ^^^^^^^ index out of bounds: the length is 3 but the index is 3
}

However, we can provide a dynamic variable (e.g., get it from the user) and the program will panic with an out of bounds access.

String Literals

So I had this problem above, when you create something like this let a = "whatever"; you are creating a string literal or &str which is a read-only string and not the same as the type String.

So when I wanted to annotate the type for the same array like this, I got an error:

let arr2: [str, 3] = ["one", "two", "three"];
                      ^^^^^ expected `str`, found `&str`

So we have to annotate it like this with &str but I had another problem printing it:

let arr2: [&str, 3] = ["one", "two", "three"];
println!("arr2 = {}", arr2);
                      ^^^^ `[&str; 3]` cannot be formatted with the default formatter
  = help: the trait `std::fmt::Display` is not implemented for `[&str; 3]`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

We can use the note to pretty print:

println!("arr2 = {:?}", arr2);
// arr2 = ["one", "two", "three"]

println!("arr2 = {:#?}", arr2);
// arr2 = [
//     "one",
//     "two",
//     "three",
// ]

Functions

Similar to other languages.

fn main() {
    my_func(10, 'a');
}

// we can define parameters.
fn my_func(param1: i32, param2: char) {
    println!("param1 = {}, param2 = {}", param1, param2);
    // param1 = 10, param2 = a
}

The last expression in the function can act as the return value.

blocks of code evaluate to the last expression in them, and numbers by themselves are also expressions

fn main() {
    println!("double_me(1) = {}", double_me(1)); // double_me(1) = 2
}

// return values
fn double_me(x: i32) -> i32 {
    x + 1
}

However, if we change it to x + 1; it becomes an statement and we get an error.

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

Instead, we can use the return keyword.

fn main() {
    println!("double_me(1) = {}", double_me(1));
}

// return values
fn double_me(x: i32) -> i32 {
    return x + 1;
}

if-else

Similar to other languages.

fn main() {
    let number = 3;

    if number < 5 {
        println!("less than five");
    } else if number == 5 {
        println!("equals five");
    } else if number > 5 {
        println!("more than five");
    }
}

Doing unneeded parentheses around the condition returns a warning (e.g., if (number < 5)):

  |
4 |     if (number < 5) {
  |        ^          ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
4 -     if (number < 5) {
4 +     if number < 5 {
  | 

We can use if in a let statement:

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {}", number);
}

loop

We've already seen it. loop can use break and continue to leave to go back to the start. When loops are nested, these only apply only to their parent loop.

We can also have labeled loops so we can interact with outer loops

// ch03/labeled_loop.rs
fn main() {
    let mut count = 0;
    'parent_loop: loop {
        println!("count = {} in 'parent_loop", count);

        loop {
            println!("count = {} in inside loop", count);
            if count == 2 {
                // break out of the 'parent_loop
                break 'parent_loop;
            }
            count += 1;
        }
    }
    println!("End count = {}", count);
}

// count = 0 in 'parent_loop
// count = 0 in inside loop
// count = 1 in inside loop
// count = 2 in inside loop
// End count = 2

We can also return values from loop (lol wut?). We can assign the loop to a variable and then return break value;.

// ch03/return_value_loop.rs
fn main() {
    let mut count = 0;

    let counted = loop {
        count += 1;
        if count == 5 {
            break count;
        }
    };

    println!("counted = {}", counted); // counted = 5
}

while

loops don't have conditions. We can use while instead.

// ch03/while.rs
fn main() {
    let mut count = 0;

    while count != 5 {
        count += 1;
    }

    println!("count = {}", count); // count = 5
}

Loop Through Collections with for

We can iterate through a collection (e.g., array but not tuple) with for.

// ch03/while.rs
fn main() {
    let strings = ["one", "two", "three"];

    for s in strings {
        println!("{}", s);
    }
}

Trying to use for with a tuple returns this error.

let tup: (i32, f64, u8) = (500, 6.4, 1);

for t in tup {
    println!("{}", t);
}

    |
10  |     for t in tup {
    |              ^^^ `(i32, f64, u8)` is not an iterator
    |
    = help: the trait `Iterator` is not implemented for `(i32, f64, u8)`
    = note: required because of the requirements on the impl of `IntoIterator` for `(i32, f64, u8)`
note: required by `into_iter`

The book says for is the most common way to use loops. We can use it to repeat things a certain number of times by using it over a range.

// rev is reversing the range.
fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

// 3!
// 2!
// 1!
// LIFTOFF!!!

Chapter 4

Stack: LIFO. Data on stack must have a known and fixed size. Faster.

Heap: Data with unknown size at compile time or a size that might change. You ask for a certain amount of space on heap, the memory allocator locates some free space and returns a pointer to it (and sets it in-use). Slower.

Ownership Rules

  • Each value in Rust has a variable that’s called its owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

The String Type

String literals are immutable (type &str). See the section String Literals above for more info.

We can use String which exists on the heap. We can create them from a string literal and modify it.

fn main() {
    let mut s = String::from("hello");
    
    // append something to it with push_str
    s.push_str(", world!");
    
    // we cannot do this because it's not a string literal.
    // println!(s); // Compiler error

    // print it
    println!("{}", s); // hello, world!
}

Move

When the value leaves the scope, Rust automatically calls drop at the closing curly braces. A String has three parts:

  1. ptr: Pointer to the memory with the values.
  2. len: Number of bytes used by the String.
  3. cap: Total number of bytes of memory assigned to the String by the allocator.

Let's create a String and then assign it to another variable like this:

let s1 = String::from("hello");
let s2 = s1;

The ptr in both s1 and s2 will point to the same location on the heap with the value of the string. The value is not copied for s2.

When both s1 and s2 go out of scope we might have gotten a double-free bug because both wanted to free the memory. To prevent this issue, Rust makes s1 invalid as soon as the assignment happens (let s2 = s1;). We cannot use s1 after that.

fn main() {
    // create a String.
    let s1 = String::from("hello");
    // assign it to s2
    let s2 = s1;
    // try to use s1.
    println!("{}", s1);
    // use s2 so we don't get an "unused variable warning"
    println!("{}", s2);
}

And we get an error because s1 was moved.

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:7:20
  |
3 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
4 |     // assign it to s2
5 |     let s2 = s1;
  |              -- value moved here
6 |     // try to use s1.
7 |     println!("{}", s1);
  |                    ^^ value borrowed here after move

Clone

But what if we want to make a copy? We use clone.

fn main() {
    // create a String.
    let s1 = String::from("hello");
    // clone it
    let s2 = s1.clone();
    // try to use s1
    println!("{}", s1); // hello
    // use s2 so we don't get an "unused variable warning"
    println!("{}", s2); // hello
}

Copy

But we have seen assignments in other types and both work.

fn main() {
    let x = 5;
    let y = x;
    
    // we can use both x and y.
    println!("x = {}, y = {}", x, y); // x = 5, y = 5
}

These types have a known size at compile time and are stored on the stack. So assignment here creates a new copy of the entire object on the stack.

If a type has the Copy trait, this behavior occurs: Integers, floats, chars, bools, and Tuples if the types in it have all implemented the Copy trait.

Ownership and Functions

Passing a value to a function will move or copy a value like an assignment.

// ch04/func_string.rs
fn main() {
    // create a string
    let s = String::from("hello"); 

    // pass it to a function
    use_string(s);

    // we cannot use s here anymore because it was moved to `some_string` and
    // it went out of scope when `use_string` returned.
    println!("{}", s);
}

fn use_string(some_string: String) {
    println!("{}", some_string);
    // some_string goes out of scope. drop is called. Memory is freed.
}

We get an error because s was moved when passed to the function.

error[E0382]: borrow of moved value: `s`
  --> src/main.rs:10:20
   |
3  |     let s = String::from("hello"); 
   |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
...
6  |     use_string(s);
   |                - value moved here
...
10 |     println!("{}", s);
   |                    ^ value borrowed here after move

But we won't have this issue if the type implements the Copy trait (e.g., int).

// ch04/func_int.rs
fn main() {
    // create an int
    let x = 5;

    // x does not move because i32 implements `Copy`.
    use_int(x); // 5
                
    // we can still use x here.
    println!("{}", x);  // 5
}

fn use_int(some_integer: i32) {
    println!("{}", some_integer);
} // some_integer goes out of scope. Nothing special happens.

Same thing happens with returns. If we pass a String to a function, we need to return it from the function to be able to use it later.

References

To avoid this whole mess of moving when passing variables to function we can use references. A reference refers to the variable but does not own it. So when we pass a reference to the function there will be no moves.

We call the action of creating a reference borrowing.

fn main() {
    // create a string
    let s1 = String::from("hello");
    // pass it as a reference to calculate_length
    let len = calculate_length(&s1);
    // we can still use s1 here.
    println!("The length of '{}' is {}.", s1, len);
    // The length of 'hello' is 5.
}

// note the annotation here, the param type is &String
fn calculate_length(s: &String) -> usize {
    return s.len();
    // `s.len()` would do the same
}

When s goes out of scope at the end of the function, the value for s1 is not dropped because s does not own it.

We cannot modify s inside.

fn main() {
    // create a string
    let s1 = String::from("hello");
    // pass it as a reference
    modify_s(&s1);
}

fn modify_s(s: &String) {
    s.push_str(" yolo!");
}

We get an error.

error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
 --> src/main.rs:9:5
  |
8 | fn modify_s(s: &String) {
  |                ------- help: consider changing this to be a mutable reference: `&mut String`
9 |     s.push_str(" yolo!");
  |     ^^^^^^^^^^^^^^^^^^^^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable

Let's use the suggestion and pass a mutable reference to the function.

fn main() {
    // create a string
    let s1 = String::from("hello");
    // pass it as a reference
    modify_mut_s(&mut s1);
}

fn modify_mut_s(s: &mut String) {
    s.push_str(" yolo!");
}

We get another error because s1 is not mutable we cannot borrow it as mutable.

error[E0596]: cannot borrow `s1` as mutable, as it is not declared as mutable
 --> src/main.rs:5:18
  |
3 |     let s1 = String::from("hello");
  |         -- help: consider changing this to be mutable: `mut s1`
4 |     // pass it as a reference
5 |     modify_mut_s(&mut s1);
  |                  ^^^^^^^ cannot borrow as mutable

Let's make s1 mutable, too.

fn main() {
    // create a mutable string
    let mut s1 = String::from("hello");
    // pass it as a mutable reference
    modify_mut_s(&mut s1);
    
    println!("{}", s1); // hello yolo!
}

fn modify_mut_s(s: &mut String) {
    s.push_str(" yolo!");
}

And this works! Remember that although s1 is mutable, we could have borrowed it as immutable.

Mutable References

We can only have one mutable reference to a value at a time. This code won't work:

fn main() {
    // create a mutable string
    let mut s1 = String::from("hello");
    
    let r1 = &mut s1;
    let r2 = &mut s1;

    println!("{}, {}", r1, r2);
}

We get this error:

error[E0499]: cannot borrow `s1` as mutable more than once at a time
 --> src/main.rs:6:14
  |
5 |     let r1 = &mut s1;
  |              ------- first mutable borrow occurs here
6 |     let r2 = &mut s1;
  |              ^^^^^^^ second mutable borrow occurs here
7 | 
8 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

This supposedly helps with data races.

We cannot also have mutable and immutable borrows at the same time.

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{}, {}, and {}", r1, r2, r3);
}

Because we have an immutable reference in the same scope. Multiple immutable references are allowed.

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // BIG PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 | 
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrow later used here

Reference Scope

The scope of a reference starts from where it's introduced until the last time it is used even though the block has not finished. It's a bit different from variable references. E.g., this will work because r1 and r2 are not used after r3 is created.

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{} and {}", r1, r2);
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{}", r3);
}

Dangling Reference

The compiler does not allow us to create dangling references. This won't work:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

dangle creates s and wants to return a reference to it. However, after dangle returns, s goes out of scope and is deallocated. Hence, a dangling reference. The compiler returns an error. Instead, we can return the s.

this function's return type contains a borrowed value, but there is no value for
it to be borrowed from help: consider using the `'static` lifetime

The Slice Type

Similar to slice in Go? You can reference a sequence in a collection without ownership.

Small function to find the index of the first word in a string.

fn first_word(s: &String) -> usize {
    // converts the string to an array of bytes
    let bytes = s.as_bytes();

    // bytes.iter.enumerate() returns a tuple. The index and a reference to the item
    // here we are destructuring the tuple
    for (i, &item) in bytes.iter().enumerate() {
        // compare the character with space
        if item == b' ' {
            return i;
        }
    }

    // if there's no space in the string, all of it is the word
    s.len()
}

However, this is not useful because it's an index to a string that might have been modified since then. Instead, we can return a String slice with the words.

String Slice

Create it like this. Note the second index is exclusive. E.g., [0..5] starts from index 0 and ends at 4.

fn main() {
    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];

    println!("{} - {}", hello, world);
}

We can drop the lower range if we want to start from the beginning. So [0..5] and [..5] are the same.

If the higher range is the end we can drop it. E.g., [4..] goes from index 4 to the end.

[..] creates a slice from the whole string. let slice = &s[..]. Now, we can rewrite the function to return a slice.

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

String literals are slices. Remember what we saw in the string literal section? Their type is &str.

Other Slices

We can create slices of other types.

fn main() {

    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3];
    
    println!("{:?}", slice);    // [2, 3]
}

Chapter 5

Structs

Similar to other programming languages.

// ch05/basic_struct.rs
// define a struct
struct Game {
        name: String,
        hours_played: u32,
        path: String,
}

fn main() {

    // create a mutable object
    let mut game1 = Game {
        name: String::from("Windows Calculator"),
        hours_played: 123,
        path: String::from("C:/Windows/System32/calc.exe"),
    };
    
    // access fields
    println!("{}, {}, {}", game1.name, game1.hours_played, game1.path);
    // Windows Calculator, 123, C:/Windows/System32/calc.exe
    
    // change fields
    game1.path = String::from("C:\\Windows\\System32\\calc.exe");
    // print the modified field
    println!("{}", game1.path);
    // C:\Windows\System32\calc.exe
}

We cannot set specific fields to mutable. Whole object needs to be mutable or not.

Field Init Shorthand

Let's say we have this function to create a new Game object.

fn build_game(name: String, hours_played: u32, path: String) -> Game {
    Game {
        name: name,
        hours_played: hours_played,
        path: path,
    }

    // apparently using the return keyword is _bad_.
}

If the name of the field and the variable with the value is the same like the above we can do a shorthand like this.

// complete example in ch05/field_init_shorthand.rs
fn build_game(name: String, hours_played: u32, path: String) -> Game {
    Game {
        name,
        hours_played,
        path,
    }
}

Struct Update Syntax

We can create a new object with the values from a previous object and only modify some.

// complete example in ch05/struct_update.rs

fn main() {

    // create a game object
    let game1 = Game {
        name: String::from("Windows Calculator"),
        hours_played: 123,
        path: String::from("C:/Windows/System32/calc.exe"),
    };
    
    // create game2 based on game1 with a new path
    let game2 = Game {
        path: String::from("C:\\Windows\\System32\\calc.exe"),
        ..game1
    };
    
    // access fields
    println!("{}, {}, {}", game2.name, game2.hours_played, game2.path);
    // Windows Calculator, 123, C:\Windows\System32\calc.exe
}

However, we moved game1 so we cannot use it anymore. Because we used the value of String for the name field. If we had only copied the fixed-length values of game1 then we would have been able to use it. Here's an example to show that.

Note how print_game accepts a reference, if we had passed the actual object it would have been moved after calling the function and we could not have used it anymore.

// ch05/struct_update_move.rs
// define a struct
struct Game {
    name: String,
    hours_played: u32,
    path: String,
}

fn print_game(game: &Game) {
    println!("{}, {}, {}", game.name, game.hours_played, game.path);
}

fn main() {

    // create a game object
    let game1 = Game {
        name: String::from("Windows Calculator"),
        hours_played: 123,
        path: String::from("C:/Windows/System32/calc.exe"),
    };
    
    // create game1_new based on game1 but only reuse hours_played which is the
    // only fixed-length field
    let game1_new = Game {
        name: String::from("Guild Wars"),
        path: String::from("C:/Guild Wars/gw.exe"),
        ..game1
    };
    
    // we can still use game1 here because we did not "move" any of the Strings
    // create game2 based on game1 with a new path
    print_game(&game1);
    // Windows Calculator, 123, C:/Windows/System32/calc.exe
    
    print_game(&game1_new);
    // Guild Wars, 123, C:/Guild Wars/gw.exe
    
    // create game2_new but reuse the Strings so they are "moved"
    let game1_new_new = Game {
        hours_played: 6000,
        ..game1
    };
    
    // we cannot use game1 anymore.
    print_game(&game1);  // <-- error here
    print_game(&game1_new_new);
}

We get an error in the last print_game(&game1) because game1 was partially moved when creating game1_new_new.

error[E0382]: borrow of partially moved value: `game1`
  --> src/main.rs:44:16
   |
38 |       let game1_new_new = Game {
   |  _________________________-
39 | |         hours_played: 6000,
40 | |         ..game1
41 | |     };
   | |_____- value partially moved here
...
44 |       print_game(&game1);  // <-- error here
   |                  ^^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `game1.path` has type `String`, which does not implement the `Copy` trait

Tuple Structs

Think of them as structs but without field names. We can access the fields by index (see in print_game).

// ch05/tuple_struct.rs

// define two tuple structs
struct Game(String, u32, String);
struct App(String, u32, String);

fn print_game(game: &Game) {
    // we can access the tuple struct's fields with the index
    println!("{}, {}, {}", game.0, game.1, game.2);
}

fn main() {

    let game1 = Game(
        String::from("Guild Wars"),
        5000,
        String::from("C:/Guild Wars/gw.exe")
    );
    
    print_game(&game1);
    // Guild Wars, 5000, C:/Guild Wars/gw.exe
    
    let app1 = App(
        String::from("Windows Calculator"),
        123,
        String::from("C:/Windows/System32/Calc.exe")
    );
    
    // we cannot call print_game with &App because they are different structs
    // although they have the same fields
    print_game(&app1);  // <-- error 'expected struct `Game`, found struct `App`'
}

Game and App are different structs although they have similar fields.

Unit-Like Structs

They don't have any fields.

struct Whatever;

let wt = Whatever;

Derived Traits

We cannot use println! to print the Game struct.

struct Game {
    name: String,
    hours_played: u32,
    path: String,
}

fn main() {

    let game1 = Game {
        name: String::from("Guild Wars"),
        hours_played: 5000,
        path: String::from("C:/Guild Wars/gw.exe")
    };
    
    println!("{}", game1);
}

We get an error in println! because it does not implement the std::fmt::Display trait.

error[E0277]: `Game` doesn't implement `std::fmt::Display`
  --> src/main.rs:15:20
   |
15 |     println!("{}", game1);
   |                    ^^^^^ `Game` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Game`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

The error tells us we can use println!("{:?}", game1); which results in another error:

error[E0277]: `Game` doesn't implement `Debug`
  --> src/main.rs:15:22
   |
15 |     println!("{:?}", game1);
   |                      ^^^^^ `Game` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Game`
   = note: add `#[derive(Debug)]` to `Game` or manually `impl Debug for Game`
   = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

Let's add #[derive(Debug)] to Game.

#[derive(Debug)]
struct Game {
    name: String,
    hours_played: u32,
    path: String,
}

fn main() {

    let game1 = Game {
        name: String::from("Guild Wars"),
        hours_played: 5000,
        path: String::from("C:/Guild Wars/gw.exe")
    };
    
    println!("{:?}", game1);
}

Finally: Game { name: "Guild Wars", hours_played: 5000, path: "C:/Guild Wars/gw.exe" }.

The dbg Macro

We can use the dbg! macro to print to stderr (println! prints to stdout). It prints info about the parameter and returns their ownership. We can also pass references to it.

#[derive(Debug)]
struct Game {
    name: String,
    hours_played: u32,
    path: String,
}

fn main() {

    let game1 = Game {
        name: String::from("Guild Wars"),
        hours_played: dbg!(5000),
        path: String::from("C:/Guild Wars/gw.exe")
    };
    
    dbg!(&game1);
}

The result:

[src/main.rs:12] 5000 = 5000
[src/main.rs:16] &game1 = Game {
    name: "Guild Wars",
    hours_played: 5000,
    path: "C:/Guild Wars/gw.exe",
}

Methods

We can convert print_game into a method that we can call on Game objects. They are defined similar to normal functions, but their first parameter is always self.

// ch05/method1.rs
struct Game {
    name: String,
    hours_played: u32,
    path: String,
}

impl Game {
    // implement print for Game
    fn print(&self) {
        println!("{}, {}, {}", self.name, self.hours_played, self.path);
    }
}

fn main() {

    let game1 = Game {
        name: String::from("Guild Wars"),
        hours_played: 5000,
        path: String::from("C:/Guild Wars/gw.exe"),
    };
    
    game1.print();
    // Guild Wars, 5000, C:/Guild Wars/gw.exe
}

Note how we are passing &self to print. Methods can also take ownership of self and other parameters.

We can name a method the same as a field. Usually, these are getters and return the value of the field.

impl Game {
    fn name(&self) -> String {
        self.name
    }
}

Now, we can call game1.name() to get the value of name.

Automatic Referencing and Dereferencing

When calling methods, Rust automatically add &, &mut, or * to the object to match the method signature. Hence, why we did not do (&game1).print() although the receiver was &self.

Associated Functions

Functions inside an impl block are called associated functions because they are associated with a struct.

We can define non-method associated functions (they do not have the &self parameter). Let's add a function that creates a game object for calc.

// ch05/associated_method_calc.rs
struct Game {
    name: String,
    hours_played: u32,
    path: String,
}

impl Game {

    fn print(&self) {
        println!("{}, {}, {}", self.name, self.hours_played, self.path);
    }

    fn calc() -> Game {
        Game {
            name: String::from("Windows Calculator"),
            hours_played: 0,
            path: String::from("C:/Windows/System32/calc.exe"),
        }
    }
}

fn main() {
    let calc = Game::calc();
    calc.print();
    // Windows Calculator, 0, C:/Windows/System32/calc.exe
}

We can call it with Game::calc() similar to String::from(...).

We can have multiple impl blocks.

Chapter 6

Define an Enum

We can create an enum:

enum AppType {
    Utility,
    Game,
}

// get an enum
let util = AppType::Utility;

We can define functions that take a parameter of type AppType. Revamping the game example from before:

// ch06/enum1.rs

// needed to print the AppType values
#[derive(Debug)]
enum AppType {
    Utility,
    Game,
} 

struct App {
    name: String,
    hours_played: u32,
    path: String,
    app_type: AppType,
}

impl App {

    fn print(&self) {
        println!(
            "Name: {}, Hours played: {}, Path: {}, Type: {:?}",
            self.name,
            self.hours_played,
            self.path,
            self.app_type
        );
    }
}

fn main() {

    let calc = App {
        name: String::from("Windows Calculator"),
        hours_played: 123,
        path: String::from("C:/Windows/System32/calc.exe"),
        app_type: AppType::Utility,
    };

    calc.print();
    // Name: Windows Calculator, Hours played: 123, Path: C:/Windows/System32/calc.exe, Type: Utility
}

Note how I have used #[derive(Debug)] before the enum to print its value with {:?}. Otherwise it does not work.

We can also ditch the struct and do everything in the enum.

// ch06/enum2.rs
#[derive(Debug)]
enum AppType {
    // name and path.
    Utility(String, String),
    // name, path, hours_played
    Game(String, String, u32),
}

fn main() {
    let calc = AppType::Utility(
        String::from("Windows Calculator"),
        String::from("C:/Windows/System32/calc.exe"),
    );

    let gw = AppType::Game(
        String::from("Guild Wars"),
        String::from("C:/Guild Wars/gw.exe"),
        5000,
    );

    println!("{:?}", calc);
    // Utility("Windows Calculator", "C:/Windows/System32/calc.exe")
    
    println!("{:?}", gw);
    // Game("Guild Wars", "C:/Guild Wars/gw.exe", 5000)
}

We can also define structs and pass them as a parameter to the enum function.

// ch06/enum3.rs
#[derive(Debug)]
struct GameStruct {
    name: String,
    path: String,
    hours_played: u32,
}

#[derive(Debug)]
struct UtilStruct {
    name: String,
    path: String,
}

#[derive(Debug)]
enum AppType {
    Game(GameStruct),
    Utility(UtilStruct),
}

fn main() {
    let calc = AppType::Utility(UtilStruct {
        name: String::from("Windows Calculator"),
        path: String::from("C:/Windows/System32/calc.exe"),
    });
    
    println!("{:?}", calc);
    // Utility(UtilStruct { name: "Windows Calculator", path: "C:/Windows/System32/calc.exe" })
}

We can have different things in the enum like the example from the book:

enum Message {
    // no data associated with it.
    Quit,
    // named fields like a struct
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

Enum Methods and match

We can also have enum methods. Let's define the new print method that returns a String that represents each enum.

// ch06/enum4.rs
enum AppType {
    Game(GameStruct),
    Utility(UtilStruct),
}

impl AppType {
    fn stringer(&self) -> String {
        match self {
            AppType::Game(g) => format!(
                "Name: {}, Path: {}, Hours Played: {}",
                g.name, g.path, g.hours_played
            ),
            AppType::Utility(u) => format!("Name: {}, Path: {}", u.name, u.path),
        }
    }
}

Note how we are using match and are able to access the enum parameters (in this case structs). The format! macro returns a formatted String.

Option Enum

Rust does not have null but the Option enum allows us to have None.

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

We can use Some and None directly without importing anything. <T> == lol yes generics.

// Option<i32>
let some_number = Some(5);
let absent_number: Option<i32> = None;  // we have to enter the type for None

// Option<&str>
let some_string = Some("a string");

Matching with Option

We can try to write a function that extracts the value of u32 from Option<i32> like this:

fn extract_option(o: Option<u32>) -> u32 {
    match o {
        Some(i) => i,
    }
}

This returns i for Some(i), but the compiler complains about not handling None.

error[E0004]: non-exhaustive patterns: `None` not covered
   --> src/main.rs:2:11
    |
2   |     match o {
    |           ^ pattern `None` not covered
    |

Basically, the match should cover all possible values. They are exhaustive. Here, we will have a dilemma. What should we return as None? None is the absence of values here. A very naive way would be returning 0. But that means Some(0) and None would be the same.

fn extract_option(o: Option<u32>) -> u32 {
    match o {
        Some(i) => i,
        None => 0,
    }
}

fn main() {
    let s1 = Some(0);
    let n1: Option<u32> = None;
    
    println!("{}", extract_option(s1)); // 0
    println!("{}", extract_option(n1)); // 0
}

I still don't know how to use None, but let's move on.

Writing exhaustive matches will be boring if we only want to take action for a few values. This is why we have other. So, we can write matches like this.

match user_input {
    1 => {
        do1();
        launch();
    } // we can have a block here, too - rustfmt removes the colon here
    2 => abort(),
    other => try_again(other), // note how we can use `other` in the arm
}

If we don't want to use the value, we can use _ instead of other.

match user_input {
    1 => {
        do1();
        launch();
    }
    2 => abort(),
    _ => try_again(), // run try_again for all other values
}

If we really don't want to do anything, we can replace try_again() with ():

match user_input {
    1 => {
        do1();
        launch();
    }
    2 => abort(),
    _ => (), // don't do anything for all other values
}

if let

Well, this is very confusing!

Similar to a match but not exhaustive. These two are supposedly the same.

let config_max = Some(3);
match config_max {
    Some(max) => println!("The maximum is configured to be {}", max),
    _ => (),
}

Because of the let, the value of max will be 3u8 after the if.

let config_max = Some(3);
if let Some(max) = config_max {
    println!("The maximum is configured to be {}", max);
}

This gets more confusing because if we already have a max variable, it will be shadowed here.

fn main() {
    let config_max = Some(3);
    let max = 10;
    println!("{}", max); // prints "10"

    if let Some(max) = config_max {
        println!("{}", max); // prints "3"
    }
    println!("{}", max); // prints "10"
}

I should stop thinking of this as an if. Not sure how I can handle using it later when writing Rust. We can also have else here which is like the _ in match and matches everything else.

fn main() {
    let config_max = Some(3);
    if let Some(max) = config_max {
        println!("{}", max); // prints "3"
    } else {
        println!("{}", "not 3");
    }
}

Chapter 7

Packages & Crates

  • Crate: A binary of library.
  • Crate root: The source file the compiler starts from.
    • src/main.rs: Crate root of binary crate with the same name as the package.
      • More binary crates will be in src/bin.
    • src/lib.rs: Crate root of library crate with the same name as the package.
  • Package: One or more crates.
    • Has Cargo.toml which describes how to build the crates.
    • Has at most one library crate and many binary crates.
    • Must have at least one crate.

Modules

Modules help us organize code within a crate into groups. Also public/private items. Defined with the mod keyword. Modules can be nested, too.

mod applications {
    mod games {
        fn start() {}
    }
    mod utilities {
        fn start_utility() {}
    }
}

games and utilities are sibling modules. They are defined in the same module and have the same parent. All top level modules are children of an implicit module named crate. In this code applications is a child of crate.

Paths

How to tell Rust where to find an item in the module tree. Separator is ::.

  • Absolute: Starts with the create name or crate.
  • Relative: Starts from the current module and uses self, super, or an identifier in the current module.
// ch07/restaurant/src/lib.rs
mod applications {
    mod games {
        fn start() {}
    }
    mod utilities {
        fn start_utility() {}
    }
}

// pub makes it public, more about it later
pub fn run_game() {
    // absolute path
    crate::applications::games::start();

    // relative path
    applications::games::start();
}

applications is defined in the same level as run_game so we can start with it in a relative path.

We cannot build this with cargo build because the games module is private.

error[E0603]: module `games` is private
  --> src/lib.rs:13:26
   |
13 |     crate::applications::games::start();
   |                          ^^^^^ private module
   |
note: the module `games` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod games {
   |     ^^^^^^^^^

Rust's Privacy Boundary

Everything is private by default in Rust. We have to use pub to make them public.

Parent modules cannot see items in their child modules, but child modules can use items in their parent modules.

If we just make the games module public (pub mod games) we still get an error because start is still private. It must be public, too.

mod applications {
    pub mod games {
        pub fn start() {}
    }
    mod utilities {
        fn start_utility() {}
    }
}

// now this works
// crate::applications::games::start();

Why can we call stuff in the applications module although it's not public? Because run_game() is defined in the same module.

Super

Equal to .. in the file system. We can refer to parent modules. delete_stuff is defined in applications. We can call it super from inside games.

mod applications {
    pub mod games {
        pub fn start() {}

        fn delete() {
            // call delete_stuff() from `applications`
            super::delete_stuff();
        }
    }
    mod utilities {
        fn start_utility() {}
    }

    fn delete_stuff() {}
}

Public Structs

We can make structs public with pub but the fields will stay private unless we manually make them public, too.

// ch07/applications/src/lib.rs
mod applications {
    pub mod games {
        // public struct
        pub struct Game {
            pub name: String,   // public field
            hours_played: u32,  // private field
        }
    }
}

pub fn create_guild_wars() -> Game {
    applications::games::Game {
        name: String::from("Guild Wars"),
        hours_played: 5000,  // error here because the field is private
    }
}

We cannot access hours_played inside because it's a private field.

error[E0451]: field `hours_played` of struct `Game` is private
  --> src/lib.rs:14:9
   |
14 |         hours_played: 123,
   |         ^^^^^^^^^^^^^^^^^ private field

If we want to keep the field private, we need to create getters and setters for it. Another solution is moving the create_calc() function to the games module. It will be adjacent to the Game struct and can use its fields. This will work even if Game is not public but we probably want to use it outside of the module in other parts of the program.

mod applications {
    pub mod games {
        // public struct
        pub struct Game {
            name: String,
            hours_played: u32,
        }
        pub fn create_guild_wars() -> Game {
            Game {
                name: String::from("Guild Wars"),
                hours_played: 5000,
            }
        }
    }
}

Public Enums

Making an enum public will make all its variants public, too. We can use Utility and Game because AppType is public.

// ch07/enums/src/lib.rs
mod applications {

    pub mod app_enums {
        // public enum
        pub enum AppType {
            Utility,
            Game,
        }
    }
}

fn create_enums() {
    let calc = applications::app_enums::AppType::Utility;
    let gw = applications::app_enums::AppType::Game;
}

The use Keyword

We can bring paths into our scope with use. We don't have to write the complete path. We can use both absolute and relative paths.

mod applications {
    pub mod app_enums {
        // public enum
        pub enum AppType {
            Utility,
            Game,
        }
    }
}

// absolute path
// use crate::applications::app_enums::AppType;

// can also use relative paths
// use applications::app_enums::AppType;
// or use self in the relative path
use self::applications::app_enums::AppType;

fn create_enums() {
    let calc = AppType::Utility;
    let gw = AppType::Game;
}

Note how we can use self in the relative path. The book uses self but removing it did not cause an issue for me.

We can also bring Utility and Game directly to scope, but that is not recommended. By using AppType we show they are not locally defined and it helps us discover where they are defined.

// this works but not recommended
use applications::app_enums::AppType::Utility;
use applications::app_enums::AppType::Game;

fn create_enums() {
    let calc = Utility;
    let gw = Game;
}

when bringing in structs, enums, and other items with use, it’s idiomatic to specify the full path.

If there are two items with the same name then we can use the parents.

Or we can use the as keyword to bring an item to the current scope with a different name. Doesn't make sense here, but good for demonstration.

use applications::app_enums::AppType as ApplicationType;

fn create_enums() {
    let calc = ApplicationType::Utility;
    let gw = ApplicationType::Game;
}

pub use

Items brought into the current scope are private to external code. E.g., we have Config struct imported and we want code that uses are our module to be able to access it.

pub use configs::Config;

fn use_config(c: Config) {
    // do something
}

Nested Paths in use

If we are using items from the same path.

mod applications {
    pub mod games {
        fn start() {}
    }
    pub mod utilities {
        fn start_utility() {}
    }
}

use applications::{games, utilities};

We can also use the glob operator * to import everything public under a path. Useful in tests but not anywhere else: use std::collections::*;.

Modules in Different Files

We're gonna refactor our applications crate in the applications_separate_files crate.

// ch07/applications_separate_files/lib.rs

// declare the `applications` module, content will be in `applications.rs`.
mod applications;

use applications::games::{create_guild_wars, Game};

fn create_gw() -> Game {
    // do something
    create_guild_wars()
}

mod applications; in the file tells Rust to look for the contents of this module in applications.rs (in the same file path).

// ch07/applications_separate_files/applications.rs
pub mod games {
    // removed
}

pub mod utilities {
    // removed
}

We could also move games and utilities into their own files.

// ch07/applications_separate_files/applications.rs
pub mod games;

pub mod utilities;

Then put the code in applications/games.rs and applications/utilities.rs respectively. Note they will be inside the applications directory.

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── applications
    │   ├── games.rs
    │   └── utilities.rs
    ├── applications.rs
    └── lib.rs

Chapter 8

Vectors

Vev<T> or vectors. Store values of the same type sequentially in memory. All vector elements are destroyed when vector goes out of scope.

// empty vector of u32 values
let mut v: Vec<u32> = Vec::new();
// add values with push
v.push(1);
v.push(2);

// Rust can infer the type with the vec! macro, it will be i32 here
let v2 = vec![1, 2, 3];

Read Vector Values

There are two ways to read vector elements.

  1. Index
  2. .get

Index starts from zero and is similar to other languages.

fn main() {
    let mut v: Vec<u32> = Vec::new();
 
    v.push(0);
    v.push(1);
    
    let nem = v[1];
    println!("nem = {}", nem);      // 1
    println!("v[1] = {}", v[1]);    // 1
    let bem = &v[1];
    println!("^v[1] = {}", nem);    // 1
}

Using let nem = v[1]; is not really a good thing. We are looking at u32 which is a fixed-length type and hence it's not moved. Let's try with strings.

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    let nem = v[1];             // error here
    println!("nem = {}", nem);
    println!("v[1] = {}", v[1]);
    let bem = &v[1];
    println!("^v[1] = {}", nem);
}

We get an error because we cannot move and index of a vector (v[1] in this case).

error[E0507]: cannot move out of index of `Vec<String>`
  --> src/main.rs:11:15
   |
11 |     let nem = v[1];
   |               ^^^^
   |               |
   |               move occurs because value has type `String`, which does not implement the `Copy` trait
   |               help: consider borrowing here: `&v[1]`

Instead we must borrow it.

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    let nem = &v[1];    // borrow instead of move
    println!("nem = {}", nem);      // nem = 1
    println!("v[1] = {}", v[1]);    // v[1] = 1
    let bem = &v[1];
    println!("^v[1] = {}", nem);    // ^v[1] = 1
}

With the get method we receive an Option<T> (Option<String> here).

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    let index = 0;
    let v0 = v.get(index);
    match v0 {
        Some(value) => println!("v[{}] = {}", index, value),    // v[0] = 0
        None => println!("v[{}] = None", index),
    }
}

This method is usually better because it returns None if the index is out of range. We can catch it. E.g., changing index to 10 gives us:

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    let index = 10;
    let v0 = v.get(index);
    match v0 {
        Some(value) => println!("v[{}] = {}", index, value),
        None => println!("v[{}] = None", index),    // v[10] = None
    }
}

However, v[10] using the index will panic.

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    let index = 10;
    println!("v[{}] = {}", index, v[index]);
    // thread 'main' panicked at 'index out of bounds: the len is 2 but the
    // index is 10', src/main.rs:8:35
}

We cannot have both mutable and immutable references to even different elements of the vector.

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    let immutable_ref = v.get(0);
    
    // add something to the vector
    v.push(String::from("2"));
    
    // use the immutable reference <-- error
    match immutable_ref {
        Some(val) => println!("{}", val),
        None => (),
    }
}

We will get an error when we use immutable_ref after modifying the vector. Even though our reference is to a specific element. We cannot modify the vector if we have an immutable reference to any of its elements. Because we might have to allocate more memory so the vector is moved in memory and the reference to element is not valid anymore.

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
  --> src/main.rs:10:5
   |
7  |     let immutable_ref = v.get(0);
   |                         -------- immutable borrow occurs here
...
10 |     v.push(String::from("2"));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
13 |     match immutable_ref {
   |           ------------- immutable borrow later used here

Iterate over Vector Elements

We can use a for loop.

fn main() {
    let mut v: Vec<String> = Vec::new();
 
    v.push(String::from("0"));
    v.push(String::from("1"));
    
    // modify elements with mutable references
    for s in &mut v {
        (*s).push_str("0");
        
        // s.push_str("0");        
        // also works because of automatic dereferencing
    }
    
    // print all elements
    for s in &v {
        println!("{}", s);  // 00 10
    }
}

Because we are iterating over references we need to dereference s. However, we are calling a method on it to modify it so automatic dereferencing means we can just do s.push_str(...) and it will work.

Using an Enum to Store Multiple Types

Vectors can only store values of the same type, but we can define an enum with different types and then store values of that enum type in the vector.

This works if we know what types will be added to the vector at compile time.

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

Remove Vector Values

We can remove the last value with pop and get an Option<T> or None if the vector is empty.

fn main() {
    let mut v: Vec<String> = Vec::new();

    v.push(String::from("0"));
    println!("{}", extract_vector_value(v.pop()));  // "0"
    println!("{}", extract_vector_value(v.pop()));  // "None"
}

fn extract_vector_value(s: Option<String>) -> String {
    match s {
        Some(st) => st,
        None => String::from("None"),
    }
}

There are more methods. See https://doc.rust-lang.org/std/vec/struct.Vec.html.

Strings

Mostly talking about String in this section. This type is UTF-8 encoded.

// create a new String
let mut s1 = String::new();

// convert a string literal to a String
let s1_lit = "Guild Wars";
let s2 = s1_lit.to_string();    // String type

// directly convert a string literal to String
let s3 = "Guild Wars".to_string();
// we've already seen `from`, too
let mut s4 = String::from("Guild Wars");

// append a string literal to a String
s4.push_str(" 2");  // "Guild Wars 2"
// append a single character to a String
s4.push('!');       // "Guild Wars 2!"

We can also concat String with +. The caveat is that first operand must be a String and second must be &str and the result is another String. The first operand will be moved, too.

fn main() {
    let first = String::from("Guild Wars");
    let second = String::from(" Rocks!");

    println!("{}", first + &second);    // "Guild Wars Rocks!"
    // first has been moved and we cannot use it anymore
}

We can also use &String because the compiler can coerce it to &str. Rust uses a deref coercion to convert &second to &second[..]. second is not moved.

Rust Strings do not support indexing. We cannot do st[1].

Don't slice Strings. Depending on how the String is sliced, the return value could be different.

We can iterate over String bytes, but each byte might not be a valid char depending on the length of each char encoded in UTF-8. E.g., it could be encoded in 2 bytes and grabbing the first byte does not give us the actual character.

for c in "whatever".bytes() {
    println!("{}", c);
}

It works here because each char in the string above is encoded as one byte in UTF-8.

HashMaps

HashMap<K, V> maps keys of type K to values of type V.

use std::collections::HashMap;

let mut hm = HashMap::new();

// by passing these values the type will be HashMap<String, i32>
hm.insert(String::from("key0"), 0);
hm.insert(String::from("key1"), 1);

We can also create a HashMap by using two vectors. One will be key and the other will be the values.

use std::collections::HashMap;

let keys = vec![String::from("key0"), String::from("key1")];
let values = vec![0, 1];

let mut hm: HashMap<_, _> = keys.into_iter().zip(values.into_iter()).collect();

HashMaps and Ownership

Variable length values will be moved when they are stored in the HashMap. HashMaps are also stored on the heap because their size is unknown at compile time.

Reading HashMaps

We can read the values with .get(&key). Result is Option<V>. Some(value) if the key exists and None if it doesn't.

fn main() {
    use std::collections::HashMap;

    let mut hm = HashMap::new();
    hm.insert(String::from("key0"), 0);
    hm.insert(String::from("key1"), 1);

    let val = hm.get(&String::from("key0"));

    print_option(val);                          // 0
    print_option(hm.get(&String::from("123"))); // "None"
}

fn print_option(o: Option<&i32>) {
    match o {
        Some(val) => println!("{}", val),
        None => println!("None"),
    }
}

Iterating over the HashMap

This is similar to other languages. Be sure to pass a reference to the HashMap to for

fn main() {
    use std::collections::HashMap;

    let mut hm = HashMap::new();
    hm.insert(String::from("key0"), 0);
    hm.insert(String::from("key1"), 1);

    let val = hm.get(&String::from("key0"));

    for (k, v) in &hm {
        println!("{}: {}", k, v);
    }
}

Updating a Hash Map

Calling insert with an existing key will overwrite the value for that key. Otherwise, it will add the key-value pair.

Check if a key exists and only add it if it does not.

fn main() {
    use std::collections::HashMap;

    let mut hm = HashMap::new();
    hm.insert(String::from("key0"), 0);

    hm.entry(String::from("key0")).or_insert(-1);
    hm.entry(String::from("key1")).or_insert(1);

    println!("{:?}", hm);   // {"key0": 0, "key1": 1}
}

entry returns an enum called Entry. For an existing key it looks like this:

let nem = hm.entry(String::from("key0"));
println!({":?}", nem);

// Entry(OccupiedEntry { key: "key0", value: 0, .. })

For a non-existing key:

let bem =  hm.entry(String::from("key1"));
println!("{:?}", bem);

// Entry(VacantEntry("key1"))

or_insert uses the return value of entry to only add the key/value to the HashMap if the key does not exist and returns a mutable reference to the value (old value for existing keys and the new value for new keys). Hence, we cannot use two return values from or_insert in the same scope because we will have two mutable references to (although different) values in the hashmap.

fn main() {
    use std::collections::HashMap;

    let mut hm = HashMap::new();
    hm.insert(String::from("key0"), 0);

    let key0 = hm.entry(String::from("key0")).or_insert(-1);
    let key1 = hm.entry(String::from("key1")).or_insert(1); // error!

    println!("{:?}", key0);
    println!("{:?}", key1);
}

We will get this error:

error[E0499]: cannot borrow `hm` as mutable more than once at a time
  --> src/main.rs:8:16
   |
7  |     let key0 = hm.entry(String::from("key0")).or_insert(-1);
   |                ------------------------------ first mutable borrow occurs here
8  |     let key1 = hm.entry(String::from("key1")).or_insert(1);
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
9  | 
10 |     println!("{:?}", key0);
   |                      ---- first borrow later used here

If we get a reference from or_insert we need to dereference to use it.

fn main() {
    use std::collections::HashMap;

    let mut hm = HashMap::new();
    hm.insert(String::from("key0"), 0);

    let key0 = hm.entry(String::from("key0")).or_insert(-1);
    println!("{:?}", key0);     // 0

    *key0 += 1;
    println!("{:?}", key0);     // 1
}

Chapter 9

panic!

Ends the program and unwinds the stack (walks back and cleans up). This is expensive. To prevent clean up and just abort after panic (which is quick), add this to Cargo.toml:

[profile.release]
panic = 'abort'

Use the panic! macro:

fn main() {
    panic!("pewpew");
}

// thread 'main' panicked at 'pewpew', src/main.rs:2:5
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Recoverable Errors

The Result enum can be used to return values from functions that might return errors.

enum Result<T, E> {
    Ok(T),      // T: type of value returned in the success case
    Err(E),     // E: type of value returned in case of errors
}

Looking at std::fs::File::open and we can see:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}

The code will attempt to open a file. With success, a handle to the file is stored in f and panic! otherwise.

Matching on Different Errors

To act differently based on the error, we can match for error.kind() in the error arm.

We are checking if the kind of error is ErrorKind::NotFound and if so, we will create the file. We are also checking if the result of that also returns an error. For other errors we will just panic.

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,   // file exists
        Err(error) => match error.kind() {  // check the error
            // if file doesn't exist, create it
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,   // new file was created successfully
                Err(e) => panic!("Problem creating the file: {:?}", e), // creating a new file failed
            },
            // all other errors
            other_error => {
                panic!("Problem opening the file: {:?}", other_error)
            }
        },
    };
}

There are better ways of writing this code. We will see them in chapter 13.

unwrap and expect

The unwrap method in this code will return the value if there's no error and calls panic otherwise.

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}

// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {
// code: 2, kind: NotFound, message: "No such file or directory" }',
// src/main.rs:4:37

except is the same, but we can choose an error message to return along the default one.

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("panic!!!");
}

// thread 'main' panicked at 'panic!!!: Os { code: 2, kind: NotFound, message: "No
// such file or directory" }', src/main.rs:4:37

Propagating Errors

We can return errors from our functions. Here's a convoluted way of writing FromStr.

use std::fs::File;

fn main() {
    match string_to_boolean(String::from("true")) { // "it's true"
        Ok(val) => println!("it's {}", val),
        Err(s) => println!("{}", s),
    }

    match string_to_boolean(String::from("FALse")) { // "it's false"
        Ok(val) => println!("it's {}", val),
        Err(s) => println!("{}", s),
    }

    match string_to_boolean(String::from("trueee")) { // "not boolean"
        Ok(val) => println!("it's {}", val),
        Err(s) => println!("{}", s),
    }
}

fn string_to_boolean(s: String) -> Result<bool, String> {
    // converts a string to boolean. true and false are converted
    // (case-insensitive), everything else is an error

    match s.to_ascii_lowercase().as_ref() {
        "true" => Ok(true),
        "false" => Ok(false),
        _ => Err(String::from("not boolean")),
    }
}

Shortcut for Propagating Errors

Place a ? after a Result value to pass it on. If it's Ok then the program will continue. For Err the function will return it.

use std::fs::File;
use std::io;
use std::io::Read;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

We will return errors if either of open and .read_to_string methods encounter an error. The return value of the function with ? must match the parent function.

We can only use the ? operator in a function that returns Result or Option (more but I skipped it for these notes). If the parent function returns Option<T> we can use ? on a function inside with the same return value.


Misc

I stopped reading the book. These are my notes from creating projects.

Type Alias

Type aliases can point to a different type or we can create a new one on the fly. I mostly used it to create an alias for a type in an external crate.

type MyType = external_crate::SomeType;

type Point = (u8, u8);

https://doc.rust-lang.org/reference/items/type-aliases.html

Extension Trait

You have an external type, you want to extend it. You can add traits to it. Think of traits as interfaces.

First, we need to define the trait. By convention, these traits end in Ext.

extern crate crr;
use crr::OtherType;

trait OtherTypeExt {
    fn to_text(&self) -> String;
}

Next, implement it.

impl OtherTypeExt for OtherType {
    fn to_text(&self) -> String {
        // do something.
    }
}

Now, we can use it "like a method."

// in main.rs
let other = OtherType::new();
let text = other.to_text();

Source: https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html

Import an Adjacent Module

We have defined a custom error in error.rs.

parent-rs/
├── README.md
├── src
│   ├── error.rs
│   ├── lib.rs
│   └── utils.rs

To use this error in utils.rs: use crate::error.

Crate Level Pub

Only want to make something public for the current crate?

pub(crate) fn whatever() {}

Embed Files in the Binary

Similar to Go's embed package, we can read files during the build and use them as variables in the program.

We can use the include_bytes and include_str macros. File paths must be relative to the file. E.g., if we're inside src/main.rs then include_str!("whatever"); will try to read src/whatever.

fn main() {
    // read a file as text.
    let string_txt = include_str!("string.txt");
    // do whatever with string_txt.
    println!({string_txt});

    // read a file as binary.
    let binary_file = include_bytes!("binary.file");
    print!("{}", String::from_utf8_lossy(bytes));
}

These macros read single files and do not allow embedding a directory like a file system unlike Golang :(.

Convert Vec to Vec<&str>

// string_vec: Vec<String>
// str_vec: Vec<&str>
let str_vec = string_vec.iter().map(|s| s.as_str()).collect();

Extend a Struct in Another

Rust cannot do this. We cannot even embed as easily as it happens in Go, but here's a form of embedding.

Apparently, "implementing Deref and DerefMut traits allow the compiler to implicitly cast pointers to StructBs to pointers to StructAs."

Source: https://stackoverflow.com/a/32552688

struct StructA;

impl StructA {
    fn name(&self) -> &'static str {
        "Anna"
    }
}

struct StructB {
    a: StructA, 
    // other fields...
}

impl std::ops::Deref for StructB {
    type Target = StructA;
    fn deref(&self) -> &Self::Target {
        &self.a
    }
}

fn main() {
    let b = StructB { a: StructA };
    println!("{}", b.name());
}