Skip to content

Fight the Borrow Checker. Don't succumb to its tyranny.

License

Notifications You must be signed in to change notification settings

parsiya/fearless-concurrency

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 param 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 param). 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 param 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 becuse 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();
        }
    }