Notes from Learning Rust

2020-07-22

1 - Getting Started

Setup

I have my development environment setup... I have rustup. I should learn about managing Rust versions...

I should mention I'm reading The Rust Programming Language, but I got out of order rushing into things... I was more concerned with getting Rust compiled to WASM and running that code in the browser. Now I'm going back and properly learning how to use the language

Okay so like the second sentence in the first chapter says you can use rustup for managing Rust versions. Amazing

Oh no... it looks like there's a lot of "how to do this on Windows" in this book, that's not going to be fun

Hello, World!

Okay now I'm doing a Hello, World!

// hello_world.rs
fn main() {
    println!("Hello, world!");
}
rustc hello_world.rs
./hello_world

Pretty straight forward? The main function is special. When you run a Rust executable, that's what gets executed

Parameters go inside parentheses, brackets contain the function body

Now they're talking about rustfmt which I already have setup with vim, yay

println! calls a Rust macro... I didn't realize. The bang indicates a macro call

Strings are wrapped in "

Semicolons are important and denote the end of an expression

rustc hello_world.rs compiles the file

./hello_world runs that compiled executable

Rust is an ahead-of-time compiled language, so the executables should run on whatever machine they end up on regardless of the presence of Rust

Cargo

Cargo is both the build system and package manager. It handles building, downloading libraries, building those libraries

Cargo comes installed with Rust

Create a new project with Cargo
cargo new hello_cargo --bin

--bin tells it to make an executable

This creates a directory named hello_cargo which contains a Cargo.toml and a src/ directory containing a main.rs file which contains the Hello, World! from before

It also initializes a new git repository in the created directory

Build and run a Cargo project

From within the Cargo created directory, run

cargo build

This creates an executable at ./target/debug/hello_cargo

It also creates a Cargo.lock file for locking in dependencies

cargo run

This commands compiles and runs the code

cargo check

This just ensures the code is valid without compiling it

cargo build --release

This compiles the program with optimizations and that executable lives at target/release instead of target/debug

Compiling this way takes longer, but makes the code run faster

2 - Programming a Guessing Game

Alright, in this chapter we're implementing a guessing game where the program picks a random integer 1-100 and prompt the user for their guess... then it indicates if they guessed too high, too low, or correctly

use std::io;

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

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

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

So much stuff I don't know about here...

use std::io imports the IO library from the standard library, cool

stdin isn't in Rust's prelude, so we bring in io... can we... just bring in the one function? Like std::io::stdin? Oh, yes we can. That's chill, worked intuitively

let mut guess = String::new()... so let is a new variable. By default, variables are immutable, so mut makes it mutable. guess is the variable name. That's the left side. String is the string type from the standard library and new is a method on the type (not an instance) that creates a new string. So it gives us an empty string. And that's the right side

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

Oh okay looks like the importing goes either way... we could have simply called the function as std::io::stdin(). That makes sense

Calling stdin().read_line(...) waits for input from the user and turns it into a string... and it takes that string as an argument, in this case &mut guess. & indicates that the argument is a reference (interesting) oh okay this is so it can live in only one spot in memory. References are immutable by default, so we say &mut when the reference is mutable

Okay, onto the expect part. read_line returns a io::Result which is an enum. The variants are Ok or Err. Ok means it worked and it contains the generated value. Err means it failed and contains information about the failure

So... basically... monads

You can call expect on one of these Result monads with a message to be error logged when the program crashes because the type was Err

In this particular case, an error is likely to have come from the operating system, so that's a big oops

expect returns the value when the Result is Ok

Sooo... monads

Okay, onto the final println! call

The {} is a placeholder for the second argument. If you added another {} it would be replaced by the third argument

Now we're going to add more code

Starting with generating a random number. Rust doesn't include random number generation! Good job, Rust. A PRNG is out of scope for a good standard library

Cool, so we're bringing in rand

[dependencies]

rand = "0.3.14"

After adding that into Cargo.toml, rand will be installed and built the next time I run cargo build or cargo run

Stuff about reproducible builds thanks to Cargo.lock, cool

cargo update updates dependencies


2020-07-23

2 - Programming a Guessing Game

Okay back at it...

Stuff about extern bringing in external dependencies, great

Use of the rand library, great. Note about not knowing which traits need to be imported, okay?

Then we pull in std::cmp::Ordering and do

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

Ordering is an enum with those three values on it

match expressions have "arms" that have patterns and code that will run if the given value matches a pattern

But that code doesn't work because there's a type mismatch between the number generated by rand and the number entered from the user

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

So this is more of the same, but I didn't realize that with a mutable variable you could reassign like this. I guess it makes sense

trim gets rid of the whitespace, parse turns the string into a number

Neat. Now we're adding a loop so that the user can make multiple guesses

Taking sparser notes now because it's intuitive...

Can use break to break out of a loop and match functions can have bodies

And done

3 - Common Programming Concepts

Variables and Mutability

Variables

let x = 5 can't be reassigned, but let mut x = 5 can be

For large data structures, mutating in place can be more efficient than recreating the structure

Constants

Constants can't be mutated ever

Constants must have their type annotated

Constants can be anywhere, even in the global scope

Constants can't be set to a non-constant... like the result of a function call

const MAX_POINTS: u32 = 100_000;
Shadowing

This is when you later assign a new value to a variable that is already defined

You can do this to immutable variables? You have to use let when reassigning, then it goes back to being immutable? You can change the type of the variable? What? mut does not let you change types

Data Types

Rust is p good at infering types, but if you call a function like parse without specifying what type you're parsing the value into, you've got problems

Scalars

Rust has four primary scalar types: integers, floats, booleans, and characters

Integers

i8, u8, i64, u64, etc

isize and usize to determine number of bits based on the architecture of the machine running the code

1_000_000isize works

Hex looks like 0xff

Octal looks like 0o77

Binary looks like 0b1111_0000

Byte looks like b'A'

i32 is generally a safe bet and is fastest

Floats

2.0, 2.0f32, etc

Numeric Operations

+, -, *, /, %

Booleans

true and false

Characters

'A'

Compound Types
Tuples
    let my_tuple = ('A', 'B', 'C');
    let (a, b, c) = my_tuple;
    println!("{}", my_tuple.0)

Destructuring and accessing by index

Arrays

Arrays go to the stack instead of the heap

Vectors are generally preferable because it can shrink and grow

    let my_array = [1, 2, 3];
    println!("{}", my_array[0])

Accessing array elements

Functions

snake_case, call with parens, parameters get typed

Statements are instructions, expressions evaluate to a value

    let x = { 1 + 1 };
    println!("{}", x);

x here is 2 because the block ended with an expression

Expressions don't end with a ;

Return types get typed in the function signature like fn foo() -> i32 {

Comments
let foo = &bar; // comment
Control Flow
if x > 10 {
  ...
} else if {
  ...
} else {
  ...
}

Pretty standard, doesn't coerce values, so you need expressions that evaluate to booleans

fn foo() -> char {
    let x = if 10 > 0 { 'A' } else { 'B' };
    x
}

fn main() {
    println!("{}", foo());
}

I hope I don't ever need to use this though...

Loops
loop {}
while n < 10 {
  n = n + 1;
}
let xs = [1, 2, 3];
for x in xs.iter() {}
for x in (1..10) {}

Yay ranges

4 - Understanding Ownership

Rust's central feature is ownership, can't wait to find out what that means

Okay so Rust manages memory through an ownership system at compile time?

Stack and heap are important to understand when developing with Rust

Stack and heap are parts of the available memory at runtime

They act like normal stacks and heaps

Items in the stack must have a fixed size

Heap is slower because you have to follow a pointer to get to your data

So if you get something from the heap, do all your things with that thing at once

When you call a function with pointers, that goes onto the stack and then popped off when execution has ended

Okay, back to ownership

  • each value has a variable that's called its owner (does it really? okay, I guess I thought in expressions values would just be hanging out, but I guess under the hood yeah they're assigned to something)
  • each value has only one owner at a time
  • when the owner leaves scope, the value is dropped

Scope works like I'd expect...

Strings and strings are different

String literals are "strings" and String::from("YO") is stored on the heap

Strings stored on the heap can be mutated, unlike string literals

Okay so if you assign a variable that's in the heap to a new variable, they both point to the same place in the heap instead of actually copying the value

Uh okay and there are problems with that because it'll try to free up the memory twice when the variables go out of scope

Oh but the book lied to me, that doesn't actually happen

Rust marks the first variable as invalid and doesn't try to free it

Those invalid variables are no longer usable

But you can clone things

Sounds like it doesn't apply to scalars? Oh okay, it doesn't apply to types with the Copy trait which like basically just clones it behind the scenes

Returning values from functions also passes ownership

Prefixing an argument with & sends a referenced instead of transferring ownership... this means that the function doesn't have ownership of the value so it's not dropped when the function completes

Those borrowed variables are immutable within the function they're sent to

But &mut can be used, once per piece of data per scope. This solves for race conditions

Rust doesn't let you have dangling references, yay

You can't have a mutable reference and immutable references... yeah, makes sense

Slices let you reference contiguous bits of a collection

    let s = String::from("YO");
    let y = &s[..1];
    let o = &s[1..];

    println!("{}{}", y, o);

Structs are like objects/classes

struct Cat {
    name: String,
    age: usize,
}

fn make_cat(name: String, age: usize) -> Cat {
    Cat { name, age }
}

fn main() {
    let zemo = make_cat(String::from("Zemo"), 12);
    println!("{}", zemo.name);
}

This update syntax is weird though

struct Cat {
    name: String,
    age: usize,
}

fn make_cat(name: String, age: usize) -> Cat {
    Cat { name, age }
}

fn main() {
    let zemo = make_cat(String::from("Zemo"), 12);
    let older_zemo = Cat { age: 13, ..zemo };
    println!("{}", older_zemo.name);
}

It borrows the values from zemo to make older_zemo, so you can't reference zemo later on

struct Color(i32, i32, i32);

fn main() {
    let my_color = Color(0xBA, 0xDA, 0x55);
    println!("{}", my_color.0);
    println!("{}", my_color.1);
    println!("{}", my_color.2);
}

You can also do tuple style structs

##[derive(Debug)]
struct Color(i32, i32, i32);

fn main() {
    let my_color = Color(0xba, 0xda, 0x55);
    println!("{:?}", my_color);
}

And this is how you log arbitrary structs... {:?} and applying the Debug trait

{:#?} also works and provides linebreaks to make things look nice

Okay and now methods

struct Cat {
    name: String,
    age: i32,
}

impl Cat {
    fn speak() -> String {
        String::from("Meow!")
    }
    fn speak_age(&self) -> String {
        self.age.to_string()
    }
}

fn main() {
    let zemo = Cat {
        name: String::from("Zemo"),
        age: 13,
    };
    println!("{}", Cat::speak());
    println!("{}", zemo.speak_age());
}

Pretty straight-forward

2020-07-24

6 - Enums and Pattern Matching

enum CatStyle {
    Tuxedo,
    Tabby,
}

struct Cat {
    style: CatStyle,
    name: String,
}

fn main() {
    let z = Cat {
        style: CatStyle::Tuxedo,
        name: String::from("Zemo"),
    };
}

Pretty good and standard

And you can do this

##[derive(Debug)]
enum Shape {
    Rect { width: isize, height: isize },
    Square { width: isize },
}

impl Shape {
    fn log(&self) {
        println!("{:#?}", self);
    }
}

fn main() {
    let r = Shape::Rect {
        width: 5,
        height: 10,
    };
    let s = Shape::Square { width: 5 };
    r.log();
    s.log();
}

But I don't know if you can reference any fields on those structs in the enum from within a function implemented on the enum

And Rust doesn't have null

That's great. There's an Option monad that looks like

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

Ah cool, so you can have arguments to an enum. Type arguments only I guess. Makes sense

Uh huh, have to unwrap the monads handling both cases, checks out. Not too much word on that here, but it looks like we're going to get into the functions on enums stuff...

Oh okay, maybe not... you can do matching on their types, but no field access

Aha! You can... let me see

enum Shape {
    Rect(isize, isize),
    Square(isize),
}

impl Shape {
    fn get_area(&self) -> isize {
        match self {
            Shape::Rect(width, height) => width * height,
            Shape::Square(width) => width * width,
        }
    }
}

fn main() {
    let r = Shape::Rect(5, 10);
    let s = Shape::Square(5);
    println!("{}", s.get_area())
}

So it does work how I had expected (ish), but only with tuples, not with structs... k

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let one: Option<i32> = Some(1);
    let two = plus_one(one);

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

These are pretty good maybe monads

Matches are exhaustive, all possible cases must be handled

But you can throw in an arm like _ => () to catch the remaining cases

Or you can use if let

fn main() {
    let one: Option<i32> = Some(1);

    if let Some(i) = one {
        println!("{:?}", i + 1);
    } else {
        println!("Oops");
    }
}

I can't think of a scenario where you'd do this instead of using match

7 - Using Modules to Reuse and Organize Code

Modules are a thing

Modules are private unless you mark them public like pub mod

Cool now we're going to create a library with cargo new communicator --lib

// lib.rs
pub mod client;
pub mod network;
// client.rs
pub fn connect() {}
// network/mod.rs
pub mod server;

pub fn connect() {}
// network/server.rs

pub fn connect() {}

And this is how you handle directory structure

Can glob imports like

use TrafficLight::*;

fn main() {
  let red = Red;
  let yellow = Yellow;
  let green = Green;
}

But that seems difficult to follow, so... idk

::module::sub_module lets you start at root

super::module::sub_module lets you move up a level from where you are


2020-07-26

8 - Common Collections

Rust has a bunch of data types that are collections and unlike array and tuple, they're stored on the heap which means they can change size

Vectors

    let v: Vec<i32> = Vec::new();

When initializing an empty vector, utilize generics to specify what type of data it will contain

    let v: Vec<i32> = vec![1, 2, 3];

You can also initialize vectors with values using the vec! macro

Vectors must be made mutable in order to change them...

    let mut v: Vec<i32> = Vec::new();
    v.push(0);
    println!("{:#?}", v);

When vectors go out of scope, so do their members

    let first: &i32 = &v[0];
    println!("{:#?}", first);
    let first: Option<&i32> = v.get(0);
    println!("{:#?}", first);

Both of these methods get members of the vector... the first one panics when the value doesn't exist and the second one returns the Option monad, so yeah... I'm going to always use the second one unless I'm missing something

And you can't be reference something from a vector and then try to modify the vector because that would require borrowing the vector for both mutable and immutable uses

    let mut v: Vec<i32> = Vec::new();
    v.push(0);
    v.push(1);
    v.push(2);

    for i in &v {
        println!("{}", i);
    }

You can iterate over a vector like this

    let mut v: vec<i32> = vec::new();
    v.push(0);
    v.push(1);
    v.push(2);

    for i in &mut v {
        *i += 1;
    }

    println!("{:#?}", v);

Or you can iterate over it and mutate objects like this... which... both of these lookgross. I hope there's a functional approach somewhere nearby

The * operator here dereferences to get the value of i

enum NumbersWeLike {
    Int32(i32),
    Int64(i64),
}

fn main() {
    let mut v: Vec<NumbersWeLike> = Vec::new();
    v.push(NumbersWeLike::Int32(1));
    v.push(NumbersWeLike::Int64(2));
}

If you need mixed types in a vector, use an enum

Strings

str or string slices are the only strings in Rust

String or string literals are provided by the standard library

Both are UTF-8 encoded

The standard library also has other string types

    let mut s = String::new();

    let s = String::from("idk");
    println!("{}", s);
    let s = "idk";
    println!("{}", s);
    let s = "idk".to_string();
    println!("{}", s);

Uh okay these all appear to do the same thing in the end?

    let mut s = String::new();

    s.push_str("hell");
    println!("{}", s);
    s.push('o');
    println!("{}", s);

push_str adds a string slice to a string while push adds a character

    let one = String::from("hello");
    let two = String::from("world");

    let three = one + &two;
    println!("{}", three);

+ can concatenate two strings (one is borrowed so it's no longer accessible)

The & is there because you can only add a &str to a string, not another full on proper string like two is

+ doesn't take ownership of the second string, so it's still fair game

    let one = String::from("Hello,");
    let two = String::from(" World");
    let three = String::from("!");

    let result = format!("{}{}{}", one, two, three);
    println!("{}", result);

format! macro makes it simpler to join many strings together but uh... where's the list comprehension that would make this all very simple?

Internally, String is built on Vec

Slicing strings probably isn't a great idea because characters are stored as numbers and the codes get weird

    let s = String::from("Hello");

    for c in s.chars() {
        println!("{}", c);
    }
    for b in s.bytes() {
        println!("{}", b);
    }

But you can iterate over characters and bytes

Hash Maps

Rust has hash maps

They look to be normal hash maps

use std::collections::HashMap;

fn main() {
    let h: HashMap<&str, i32> = HashMap::new();

    println!("{:#?}", h);
}

Oh of course they aren't included in the prelude though

use std::collections::HashMap;

fn main() {
    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let scores = vec![0, 0];

    let team_scores: HashMap<_, _> = teams.iter().zip(scores.iter()).collect();

    println!("{:#?}", team_scores);
}

This is kind of cool... the _s tell Rust to infer the types for the HashMap and specifying that it's a HashMap is necessary because collect could turn it into another type

If a value has the Copy trait, a copy is put into the HashMap. If a value is owned, the HashMap becomes the owner

use std::collections::HashMap;

fn main() {
    let mut cats = HashMap::new();
    cats.insert(String::from("Zemo"), 100);
    let zemo = cats.get("Zemo");

    println!("{:#?}", zemo);
}

We can insert and get items from HashMaps and get gives us our nice little Option monad

    let mut cats = HashMap::new();
    cats.insert(String::from("Zemo"), 100);
    cats.insert(String::from("Tabitha"), 99);

    for (key, value) in &cats {
        println!("{}: {}", key, value);
    }

And we can iterate over key value pairs like this... I really hope we get less imperative soon. I get that it's good to know this stuff about the language, but I do not get why they included it in the first place

    let mut cats = HashMap::new();
    cats.insert(String::from("Zemo"), 100);
    cats.entry(String::from("Tabitha")).or_insert(99);

    println!("{:#?}", cats);

This inserts Tabitha: 99 only if Tabitha doesn't already exist in the HashMap


2020-07-27

9 - Error Handling

Rust makes you handle your errors

There are two major categories of errors in Rust: recoverable and unrecoverable

Recoverable errors are errors where it makes sense to tell the user hey we have a problem this didn't work out

Unrecoverable errors are symptoms of bugs

Rust doesn't have exceptions

Instead there's Result<T, E> for recoverable errors and the panic! macro which stops execution when there's an unrecoverable error

panic!

When you call the panic! macro, Rust cleans up and shuts down

fn main() {
    panic!("lol no");
}
thread 'main' panicked at 'crash and burn', ./errors.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The backtrace option lets you debug your code in case, for example, the panic comes from code you didn't author

Result<T, E>

This looks like another type of Maybe monad like Option, just specifically for errors... so I guess not exactly like a maybe monad because the nothing is something, it's an error

    let f = std::fs::File::open("yo.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Ain't no file there, dummy: {:?}", error),
    };

And you can do more specific matching like this

    let f = std::fs::File::open("yo.txt");

    let f = match f {
        Ok(file) => file,
        Err(ref error) if error.kind() == std::io::ErrorKind::NotFound => {
            panic!("Ain't no file there, dummy: {:?}", error)
        }
        Err(error) => panic!("I don't even know: {:?}", error),
    };

Result has methods on it like unwrap

unwrap returns the unwrapped T if it's Ok and calls panic! if it's Err

    let f = std::fs::File::open("yo.txt").unwrap();

So this is pretty much the same as before, but without you authoring the error message

And then there's expect which is the same, but you do author the error message

    let f = std::fs::File::open("yo.txt").expect("oopsie woopsie");

Instead of handling the error in a function, you can propagate the error to the caller so it can be handled there

fn open_file(path: &str) -> Result<std::fs::File, std::io::Error> {
    let f = std::fs::File::open(path);

    let f = match f {
        Ok(file) => file,
        Err(error) => return Err(error),
    };

    return Ok(f);
}

fn main() {
    let f = open_file("yo.txt").expect("oopsie woopsie");
}

I don't know why I did an exercise demonstrating this, it's obvious

The ? operator works similarly to the match expressions but it returns the valu if the Result is Ok and if the Result is Err it returns the error

fn open_file(path: &str) -> Result<std::fs::File, std::io::Error> {
    let f = std::fs::File::open(path)?;

    println!("{:#?}", f);

    return Ok(f);
}

fn main() {
    let f = open_file("yo.txt").expect("oopsie woopsie");
}

10 - Generic Types, Traits, and Lifetimes

Bunch of stuff about like DRY...

Then some stuff about generics which look like normal generics

Syntax for a borrowed vector with members of type T is &[T]

Rust uses monomorphization to fill in concrete types at compile time

Alright, now we're on to these traits I've been hearing so much about

Traits are similar to interfaces

pub trait Summary {
    fn summarize(&self) -> String;
}

Any methods describe within the trait block are necessary for types implementing the trait

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct Toot {
    pub content: String,
}

impl Summary for Toot {
    fn summarize(&self) -> String {
        format!("{}", self.content)
    }
}

fn main() {
    let t = Toot {
        content: String::from("bruh"),
    };
    println!("{}", t.summarize());
}

And this is how you implement a trait

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("Read more...")
    }
}

pub struct Toot {
    pub content: String,
}

impl Summary for Toot {
    fn summarize(&self) -> String {
        format!("{}", self.content)
    }
}

pub struct Zine {}

impl Summary for Zine {}

fn main() {
    let t = Toot {
        content: String::from("bruh"),
    };
    println!("{}", t.summarize());
    let z = Zine {};
    println!("{}", z.summarize());
}

And a default trait implementation looks like that

Aaand you can use traits as generics... so you'd say <T: Summary> if all you cared about was that the argument implemented Summary

fn foo<T, U>(t: T, u: U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
}

Here you can describe that arguments need to implement multiple Traits

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("Read more...")
    }
}

pub struct Toot {
    pub content: String,
}

impl Summary for Toot {
    fn summarize(&self) -> String {
        format!("{}", self.content)
    }
}

pub trait DoubleSummary {
    fn double_summarize(&self) -> String;
}

impl<T: Summary> DoubleSummary for T {
    fn double_summarize(&self) -> String {
        format!("{}{}", self.summarize(), self.summarize())
    }
}

fn main() {
    let t = Toot {
        content: String::from("bruh"),
    };
    println!("{}", t.double_summarize());
}

I had trouble wrapping my head around the syntax for this because I was reading wrong... this is how you implement a trait on anything that implements another trait

Alright, now we're on to lifetimes

Most of the time, lifetimes are implicit

The aim of explicit lifetimes is to prevent dangling references

fn main() {
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("{}", r)
}

In this case, x has gone out of scope, so rustc says "borrowed value does not live long enough"

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

This is how we communicate to Rust that all these different parameters and return values must have the same lifetime!

You can use lifetime annotations for structs so that they don't live longer than the values they borrow

There are cases where Rust infers lifetime annotations, these are called the lifetime elision rules

Static lifetime (&'static) lives for the duration of the application

This is used for strings which are in the compiled code and never leave


2020-07-28

11 - Writing Automated Tests

#[test] tells us that a function is a test, so the test runner runs it

asser_eq! macro asserts that two values are equal

Tests for a project are run with cargo test

##[cfg(test)]
mod tests {
    use super::*;

That super line tells Rust to bring the outer scope into our test scope so our things are available to us

assert! is used to assert that the value passed to it is true

So here's some assert! tests

##[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            length: 8,
            width: 7,
        };
        let smaller = Rectangle {
            length: 5,
            width: 1,
        };
        assert!(larger.can_hold(&smaller));
    }

    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            length: 8,
            width: 7,
        };
        let smaller = Rectangle {
            length: 5,
            width: 1,
        };
        assert!(!smaller.can_hold(&larger));
    }
}

Okay so assert_eq! asserts that two things are equal, assert_ne! asserts that two things are not equal

You can provide a second message to assert! and the like that is a string similar to the strings passed to format! and println! and then further arguments with the variables to fill into those strings

    #[test]
    #[should_panic(expected = "Oopsie Woopsie")]
    fn will_panic() {
        panic!("Oopsie Woopsie")
    }

And we can expect panics

cargo test -- --nocapture surfaces any logs from tests

There's matching for test names so you can do cargo test foo to only run tests with foo in their name

#[ignore] tells Rust to ignore a test unless you call cargo test -- --ignored

13 - Functional Language Features

Okay, cool, we're finally here

Closures look a lot like functions which makes sense because they're anonymous functions, okay

    let c = |num: isize| num;

Here's a closure that returns its only argument

Closures don't require type annotations because they aren't part of an explicit, exposed interface

use std::collections::hash_map::Entry;
use std::collections::HashMap;

struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    func: T,
    memo: HashMap<u32, u32>,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(func: T) -> Cacher<T> {
        Cacher {
            func,
            memo: HashMap::new(),
        }
    }
    fn value(&mut self, arg: u32) -> &u32 {
        match self.memo.entry(arg) {
            Entry::Occupied(e) => e.into_mut(),
            Entry::Vacant(e) => e.insert((self.func)(arg)),
        }
    }
}

fn main() {
    let mut doubler = Cacher::new(|num| num * 2);

    println!("{}", doubler.value(100));
}

Here's some memoization that I don't completely understand!

Okay now I understand it. I like that entry method a lot

Closures get traits invisibly...

FnOnce is on all closures because all closures can be called at least once FnMut is on all closures that borrow values mutably Fn is on all closures that borrow values immutably

The move keyword can be used to ensure a closure takes ownership of the values it uses

let equal_to = move |y| x == y;

Rust iterators are lazy, hooray

Methods that call next on iterators are called consuming adaptors

  let v = vec![1,2,3];
  let m: Vec<i32> = v.iter().map(|x| x * 2).filter(|x| x > &2).collect();

  println!("{:#?}", m);

Here's some mapping and filtering, not bad

collect is used due to lazy evaluation... need to actually evaluate all the things before we can get our new collection

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

fn main() {
    let counter = Counter::new();

    let c: Vec<u32> = counter.map(|x| x * 2).collect();

    println!("{:#?}", c);
}

And here we implement our own iterator


14 - More About Cargo and crates.io

Release profiles are both predefined and customizable profiles for compiling code

The dev profile is used when you run cargo build and the release profile is used when you run cargo build --release

These use the default profiles unless you define them in your Cargo.toml

[profile.dev]
opt-level = 0

opt-level specifies the number of optimizations Rust applies to your code and ranges from 0 to 3

Documentation comments use three forward slashes and look like this

/// Adds two integers
///
/// # Examples
///
///

/// assert_eq!(2, add(1, 1)); /// ``` pub fn add(x: i32, y: i32) -> i32 { x + y }


`cargo doc` generates HTML documentation from these documentation comments

`cargo doc --open` opens the documentation for the project in the browser

Examples get run as tests! Yay!

`//!` comments are also a thing, generally used in `src/lib.rs` to document the module (so they're file level)

```rs
pub use other_thing::a_function;
pub use thing::Type;

pub use lets you re-export things at the top level which also helps with documentation... it's easier for people to see what's exposed in your lib

cargo publish publishes a crate. Each release is permanent

To publish a new version you manually change the version... yuck. Will have to look into tooling around that

A [workspace] Cargo.toml defines a workspace that contains multiple Rust libraries

15 - Smart Pointers

Pointers point at other data

Smart pointers are data structures that act as pointers and have metadata and capabilities

The reference counting smart pointer type keeps track of owners of data

Smart pointers often own the data as opposed to references which just borrow data

String and Vec<T> are examples of smart pointers -- they own data and allow manipulation of that data

Smart pointers are generally structs, but they're structs that implement Deref and Drop traits

Box<T> allows you to store data on the heap instead of the stack

Boxes are used when

  • you have a type whose size isn't known at compile time but you will need to know an exact size
  • you have a large amount of data to transer ownership of and you don't want it to be copied
  • you want to own a value and you care about its traits and not its type

Boxes enable recursive types in Rust

This works because the Box takes up a constant amount of space in the stack as it's just a pointer to data in the heap

enum List {
    Cons(i32, Box<List>),
    Nil,
}

*, Deref trait, deref operator, deref coercion

    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, y);

This doesn't work because y is just pointing to x

    assert_eq!(5, *y);

Derefencing with * makes it work

    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);

This also works... just moves the reference, still needs to be dereferenced

Now we're recreating Box<T> to understand how it works

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> std::ops::Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);

    println!("{:#?}", *y);
}

Implementing the Deref trait lets us deref a type

There's also DerefMut for mutable references

You can run custom code when a value goes out of scope with the Drop trait

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

If you need to drop a value manually, before it goes out of scope, you can use std::mem::drop which prevents the double free errors you'd get if calling .drop() was allowed

The Rc<T> type counts references to a value. That value is only destroyed when the count is 0. So we can have multiple references to the same value and some of them can drop that value and others still hold onto it

So that's used for when we want to put some data into the heap and use it in multiple parts of the application aaand it doesn't work multi-threaded

Rc<T> is used pretty much like Box<T> so far as I can tell

Interior mutability lets you mutate date even when there are immutable references to that data... hokay

This is unsafe

Okay so it's like Box<T>, but RefCell<T> runs its borrowing checks at runtime

Honestly I'm skipping over a lot of things at this point... I'm just not seeing how this is applicable to someone who's just getting started with the language and I want to get to writing some real code

Rust makes it difficult to leak memory, but not impossible. Rc<T> and RefCell<T>, for example, allow you to create memory leaks


2020-07-29

16 - Fearless Concurrency

Cool now we're onto the parallel stuff

The ownership system that makes Rust good at handling memory also makes it work for parallel programming

use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("t: {}", i);
            thread::sleep(Duration::from_millis(1))
        }
    });

    for i in 1..5 {
        println!("m: {}", i);
        thread::sleep(Duration::from_millis(1))
    }
}

This runs some code in the main thread and another thread

The sleep calls are to allow both threads to work... otherwise one would take over

When the main thread completes, the other thread is stopped

    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("0: {}", i);
            thread::sleep(Duration::from_millis(1))
        }
    });

    for i in 1..5 {
        println!("m: {}", i);
        thread::sleep(Duration::from_millis(1))
    }

    handle.join().unwrap();

    println!("Done!");

Or we can do this to not stop the thread when the main thread is done, but this is blocking, so the main thread is locked up at this point

You can't use values from outside the thread inside the thread

    let handle = thread::spawn(move || {

By using the move keyword, we can force the closure that runs in the thread to take ownership of values that it's borrowing

Rust uses channels to communicate between threads

Channels have transmitters and receivers

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    let handle = thread::spawn(move || {
        let val = String::from("Hello, World!");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

tx is the transmitter and rx is the receiver

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    let handle = thread::spawn(move || {
        for i in 1..10 {
            tx.send(format!("{}", i)).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    for received in rx {
        println!("{}", received);
    }
}

Syke! This is how we really receive messages...

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx0, rx) = mpsc::channel();
    let tx1 = mpsc::Sender::clone(&tx0);

    let handle = thread::spawn(move || {
        for i in 1..10 {
            tx0.send(format!("0: {}", i)).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    let handle = thread::spawn(move || {
        for i in 1..10 {
            tx1.send(format!("1: {}", i)).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    for received in rx {
        println!("{}", received);
    }
}

And this is how you clone transmitters because, after all, mpsc means multiple producer, single consumer

Arc<Mutex<T>> can be shared between multiple threads

Atomically reference counted... okay

Sync and Send traits are used for making things work concurrently, implementing them manually is unsafe


2020-07-30

18 - Patterns and Matching

    let mut stack = Vec::new();

    stack.push(0);
    stack.push(1);
    stack.push(2);

    while let Some(top) = stack.pop() {
        println!("{}", top);
    }

while let can be used like if let and if else let

There are two types of patterns: refutable and irrefutable

let x = 0; is irrefutable because x matches anything

Some(x) is refutable because it does not match everything

    for i in 1..=30 {
        match i {
            1..=10 | 21..=30 => println!("{}", i),
            _ => (),
        }
    }

Ranges and ors are usable

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

Destructuring structs works like you'd expect it to

You can do nested destructuring on structs and enums

_ can also be used to ignore just part of a pattern like say Some(_) will match any Some

   struct Point {
        x: i32,
        y: i32,
        z: i32,
    }

    let origin = Point { x: 0, y: 0, z: 0 };

    match origin {
        Point { x, .. } => println!("x is {}", x),
    }

Here we can ignore y and z completely... why you have to account for them, I don't know

    let numbers = (1, 2, 3, 4, 5, 6, 7);

    match numbers {
        (first, second, .., sixth, seventh) => {
            println!("{}{}{}{}", first, second, sixth, seventh);
        }
    }

Can skip values with .. and then match afterward

    let x = Some(10);

    match x {
        Some(n) if n >= 10 => println!("yes"),
        _ => println!("no"),
    }

Can use guards like this to do additional checks

    let x = Some(10);

    match x {
        Some(n @ 1..=20) if n >= 10 => println!("yes"),
        _ => println!("no"),
    }

n @ 1..=20 here assigns the value contained within the Some to the variable n if it's between 1 and 20 inclusive