Rustnote

The content for this website is made using a VS Code extension: rustnote. It runs Rust code in notebook format, and saves the results to standard markdown (CommonMark).

The site is then built with a static site generator called mdbook.

My Rust projects

  • rustkernel: Takes in rust code from a VS Code notebook, runs it and returns stdout and stderror so it can be rendered in VS Code, behaves like a jupyter notebook.
  • rustnote: Uses rustkernel from VS code to run Rust code interactively inside an .md document.
  • rustnote-site: The source code for this website, including the palenight theme.
  • ech: A very simple TCP server to return the message back to the caller in a raw u8 stream, and print it to stdout. It's helpful for programs where you don't have the source code, or are trying to understand what's actually being sent out by a messy program.
  • postport: Converts Postman collections to interactive static websites (still experimental).
  • rustlings-fix: Fixes rustlings to work with rust-analyzer (language server). Pull request open for the functionality natively in rustlings here: rustlings

Format Strings in Rust 1.58

14th Jan 2022

Rust 1.58.0 Release Notes

std::fmt Documentation

The Rust 1.58.0 update brought a nice addition that will be familiar to dynamic language users.

It allows you to put variables from the outside scope directly into format string curly braces:

// In all examples below x = "world"
let x = "world";
println!("Hello {x}!");
Hello world!

You can also use format specifiers within the curly braces.

For example with debug output:

let items = vec![10, 20, 30];
println!("{items:?}")
[10, 20, 30]

Or pretty print the output:

println!("{items:#?}")
[
    10,
    20,
    30,
]

If you haven't seen it before, you can set the minimum width of how items are printed to give uniform spacing with :[width]. Example to print a table with even spacing:

let items = ["these", "words", "are", "different", "sizes"];
let column1 = "item";
let column2 = "iter";
println!("{column1:10}| {column2}");
println!("----------------");
for (i, item) in items.iter().enumerate() {
	println!("{item:10}: {i}");
}
item      | iter
----------------
these     : 0
words     : 1
are       : 2
different : 3
sizes     : 4

Align items to the centre:

println!("----------------");
for (i, item) in items.iter().enumerate() {
	println!("{item:^10}: {i}");
}
----------------
  these   : 0
  words   : 1
   are    : 2
different : 3
  sizes   : 4

Align items to right

println!("----------------");
for (i, item) in items.iter().enumerate() {
	println!("{item:>10}: {i}");
}
----------------
     these: 0
     words: 1
       are: 2
 different: 3
     sizes: 4

Set width 7 characters wide leaving 2 spaces after world:

println!("hello {x:7}!");
hello world  !

Use an existing i32 variable to do the same thing, just put a $ after the variable name

let spaces = 7;
println!("hello {x:spaces$}!");
hello world  !

Fill in gaps with any character:

println!("right aligned: hello{x:->7}!");
println!("left aligned: hello{x:-<7}!");
println!("center aligned: hello{x:-^7}!");
right aligned: hello--world!
left aligned: helloworld--!
center aligned: hello-world-!

Always print the sign of a numeric type even if positive:

let y = 10;
println!("{y:+}");
+10

Print to hex, binary or octal:

println!("hex: {y:#x}");
println!("binary: {y:#b}");
println!("octal: {y:#o}");
hex: 0xa
binary: 0b1010
octal: 0o12

Set float precision (it rounds to the set precision)

let z = 5.123456;
println!("3 precision: {z:.3}");
println!("5 precision: {z:.5}");
3 precision: 5.123
5 precision: 5.12346

You can use an existing variable to set the precision:

let precision = 3;
println!("3 precision: {z:.precision$}");
3 precision: 5.123

Chain different format specifiers together

(you can edit this cell and run it to experiment)

fn main() {
  let f = 255.555555;
  let dec = 2;
  let width = 10;
  println!("{f} to {dec} decimal places is {f:-^width$.dec$} very cool!");
}

Remember that Rust doesn't use any localization, so these outputs will always look the same.

Also to escape these curly braces, just put two of them in front of eachother:

println!("Sometimes I need to print {{ or }} too!")
Sometimes I need to print { or } too!

This quality of life improvement is significant, the first thing a programmer does when learning a new language is print output, this brings Rust on par with the most ergonomic of dynamic languages. Compiled languages can have nice things too!

Thanks for reading, if you have suggestions for things to add you can make pull requests on this file:

Github link

Variables

Immutable

const changes out values at compile time


#![allow(unused)]
fn main() {
const X: i32 = 5;
}

Mutable

let declares the variable, but you won't be able to modify it unless you set mut


#![allow(unused)]
fn main() {
let mut x = 5;
x = 6;
}

Shadow variable

Rust allows you to redeclare a variable by reusing the let keyword. This is useful when converting types, so you don't have to use prefixes or suffixes.


#![allow(unused)]
fn main() {
let spaces = "   ";
let spaces = spaces.len();

println!("{}", spaces)
}

Loops

While


#![allow(unused)]
fn main() {
let mut number = 3;
while number != 0 {
	println!("{}!", &number);
	number -= 1;
}
}
3!
2!
1!

While let

Same as while but we get a variable back if the assertion matches, so we can access top directly below:

fn main() {
    let mut stack = Vec::new();

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

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

For destructure

Also using an enumerate to get back the index


#![allow(unused)]
fn main() {
let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}
}
a is at index 0
b is at index 1
c is at index 2

For Range


#![allow(unused)]
fn main() {
for number in (1..4).rev() {
    println!("{}!", number);
}
println!("LIFTOFF!!!");
}
3!
2!
1!
LIFTOFF!!!

For iter

Print all the items in the array


#![allow(unused)]
fn main() {
let a = [10, 20, 30, 40, 50];
for e in a.iter() {
		println!("The value is: {}", e);
}
}
The value is: 10
The value is: 20
The value is: 30
The value is: 40
The value is: 50

For .len()

Print just the iterator


#![allow(unused)]
fn main() {
let x = [1, 5, 10, 20];

for i in 0..x.len() {
    println!("{}", i);
}
}
0
1
2
3

For iter enumerate over string as bytes

This example searches for a b' ' denoting a space to return to first word

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

fn main() {
		let mut s = String::from("Coolioio one yo");
        // deref coercion
		let x = first_word(&mut s);
		println!("{}", x);
}
Coolioio

Break

Break can be suffixed with an expression to return the result of the expression.


#![allow(unused)]
fn main() {
let mut counter = 0;
let x = loop {
		counter += 1;
		if counter == 10 {
				break counter * 2;
		}
};
println!("{}", x);
}
20

Break return

If the semicolon is removed from the end of the loop, we can return the result from the loop expression.

fn looper() -> i32 {
    let mut counter = 0;
    loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    }
}

fn main() {
    let x = looper();
    println!("The result is {}!", x);
}
The result is 20!

Modules

Cargo dependencies

Anything that's a dependency in Cargo.toml will be directly accessible by using the name of the crate, without the need of a use keyword:

[dependencies]
rand = "0.7.0"

#![allow(unused)]
fn main() {
let random_boolean = rand::random();
}

Include module from another file

If there is a math.rs file exposing a single function:


#![allow(unused)]
fn main() {
pub fn add(x: i32, y: i32) -> i32 {
    x + y
}
}

You can access from main.rs in the same folder:


#![allow(unused)]
fn main() {
mod math {
    include!("math.rs");
}
let result = math::add(1, 2);
}

There is a shorthand available that is used as convention:


#![allow(unused)]
fn main() {
mod math
let result = math::add(1, 2);
}

use keyword

The only purpose of use is to bring symbols into scope, making things shorter.


#![allow(unused)]
fn main() {
use math::add;
let result = add(1, 2)
}

mod.rs

A mod.rs file is a special file, the folder that it's in is considered the root and it can then expose paths from other files e.g.

- main.rs
- math/
    mod.rs
    add.rs

src/math/mod.rs:

mod add;

pub use add::add;

src/math/add.rs:


#![allow(unused)]
fn main() {
pub fn add(x: i32, y: i32) -> i32 {
    x + y
}
}

lib.rs

Putting public code inside a lib.rs file will be the entry point for the binary code inside main.rs:

src/lib.rs


src/main.rs

use libtest::math::add;

Absolute and relative paths

  • crate:: - / = root of current crate
  • super:: - ../ = from parent module, where root of crate is also considered a module
  • self:: - ./ = from self, so things like pub use self::{add::*} can be used in mod.rs

#![allow(unused)]
fn main() {
pub mod front_of_house {
		// Doesn't have to be public because it's a sibling
    mod hosting {
				// Needs to be public as it's a child we want to call from outer scope
        pub fn add_to_waitlist() {}
    }

    pub fn eat_at_restaurant() {
        // Absolute path
        crate::front_of_house::hosting::add_to_waitlist();
				// Relative path
        hosting::add_to_waitlist()
    }
}
}

Structs

Struct fields are private by default, you can't read or write from a parent. the summer() function constructs and returns a breakfast struct, but the private field still can't be used directly from any parents.


#![allow(unused)]
fn main() {
pub mod back_of_house {
    #[derive(Debug)]
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    println!("I'd like {:?} please", meal);
}
}

Enums

Enums are public by default


#![allow(unused)]
fn main() {
mod back_of_house {
    #[derive(Debug)]
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;

    println!("order1: {:?}\norder2: {:?}", order1, order2);
}
}

Use

Idiomatic to only bring in the parents of functions, so it's clear where they come from


#![allow(unused)]
fn main() {
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Adding to waitlist")
        }
    }
}

// Not idiomatic
use front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
    add_to_waitlist();
    add_to_waitlist();
    add_to_waitlist();
}

// Idiomatic
use front_of_house::hosting;
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}
}

Structs, enums, and other types it's OK to bring in the full path

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

As

If two things have the same name, the 'as' keyword can be used.


#![allow(unused)]
fn main() {
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
}

fn function2() -> IoResult<()> {
    // --snip--
}
}

pub use

This will export the used path, so in the example below hosting will be available

// lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

// main.rs
use restaurant::hosting;
fn main() {
    hosting::add_to_waitlist()
}

Nested paths

use std::{collections::HashMap, vec};

fn main() {
    let mut map = HashMap::new();
    let mut vec = vec![1, 2, 3, 4];
    vec.push(10);
    map.insert(1, 2);

    println!("{:?}\n{:?}", map, vec);
}

Over the top example of nested paths

use std::{
    fs::File,
    io::{
        self,
        prelude::{Read, Seek},
        SeekFrom,
    },
};

fn main() -> io::Result<()> {
    let mut f = File::open("./src/test.txt")?;
    let mut buffer = [0; 10];
    f.seek(SeekFrom::End(-10))?;
    let n = f.read(&mut buffer)?;
    println!("The bytes: {:?}", &buffer[..n]);
    Ok(())
}

Glob pattern

* brings in everything without having to suffix it

The above could be written as


#![allow(unused)]
fn main() {
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::io::SeekFrom;
}

Which is much nicer to look at and understand

Include another file

Putting mod in front of a filename


#![allow(unused)]
fn main() {
// src/front_of_house.rs OR
// src/front_of_house/mod.rs
pub fn add_to_waitlist() {
    println!("Wow cool yo")
}

// src/lib.rs
// mod will look for file front_of_house.rs or front_of_house/mod.rs
mod front_of_house;
pub use front_of_house::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}
}

Note that prelude is used as a convention for glob imports, basically denoting that these are all useful modules that can safely be imported together.

Error Handling

Put the Result value of read_line into a variable, then match on the enum and run logic depedning on which one it is.


#![allow(unused)]
fn main() {
let stdin_result = std::io::stdin().read_line(&mut guess);
match stdin_result {
    Ok(..) => println!("Cool one!"),
    Err(..) => println!("Oh no!"),
}
}

Shortcut methods

  • unwrap return value or call panic if error
  • expect return value or panic with custom message if error

Match on error kind / type


#![allow(unused)]
fn main() {
let f = File::open("hello.txt");
let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
        ErrorKind::NotFound => match File::create("hello.txt") {
            Ok(fc) => fc,
            Err(e) => panic!("Problem creating the file: {:?}", e),
        },
        other_error => {
            panic!("Problem opening the file: {:?}", other_error)
        }
    },
};
println!("{:?}", f)
}

More concise nested error with closure


#![allow(unused)]
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
    if error.kind() == ErrorKind::NotFound {
        File::create("hello.txt")
            .unwrap_or_else(|error| panic!("Problem creating the file: {:?}", error))
    } else {
        panic!("Problem opening the file: {:?}", error)
    }
});
}

Propogate Errors

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

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("helo.txt");
    // If error returns error back to caller of method
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    // Because last call it returns <String, io::Error>
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

fn main() {
    let f = read_username_from_file().expect("Panic with error from main");
    println!("{:?}", f);
}

? to propogate errors

This is the same as the previous example ? = Ok if all good and continue, return error to calling code if not


#![allow(unused)]
fn main() {
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
}

Even cleaner!


#![allow(unused)]
fn main() {
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)
}
}

Rust has a function already defined for this


#![allow(unused)]
fn main() {
fs::read_to_string("hello.txt");
}

NOTE

? uses the from function (trait) to convert itself to a standard error type.

Return error from main

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("hello.txt")?;
    println!("{:?}", f);
    Ok(())
}

Unwrap or else with closure


#![allow(unused)]
fn main() {
let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
}

Group errors together

So you can return a single error type and then check what specific error it was at the caller via match or if let


#![allow(unused)]
fn main() {
enum ParsePosNonzeroError {
    Creation(CreationError),
    ParseInt(ParseIntError),
}

impl ParsePosNonzeroError {
    fn from_creation(err: CreationError) -> ParsePosNonzeroError {
        ParsePosNonzeroError::Creation(err)
    }
    fn from_parse_int(err: ParseIntError) -> ParsePosNonzeroError {
        ParsePosNonzeroError::ParseInt(err)
    }
}
}

Pointers

Terms

  • value: combination of a type and relevant element
  • variable: named value slot on the stack
  • place: can hold a value somewhere in memory, either on the stack, heap, registers or disk
  • pointer: a value holding an address to a place

Example

This example is to show that a pointer has a value with an address that it's pointing to, but it also has it's own address.

Create two pointers to i32 and print the pointers, Rust will automatically dereference them:


#![allow(unused)]
fn main() {
let x = 10;
let y = 20;

let mut x_pointer = &x;
let y_pointer = &y;

println!("\nx_pointer dereferenced: {x_pointer}\ny_pointer dereferenced: {y_pointer}");
}
x_pointer dereferenced: 10
y_pointer dereferenced: 20

Print out the address that's inside the pointer using the :p format specifier:


#![allow(unused)]
fn main() {
println!("\nx address: {x_pointer:p}\ny address: {y_pointer:p}");
}
x address: 0x7ffd532910e0
y address: 0x7ffd532910e4

Print the address of the pointer value itself by creating a pointer to the pointer with & and printing it with :p:


#![allow(unused)]
fn main() {
println!("\nx_pointer address: {:p}\ny_pointer address: {:p}", &x_pointer, &y_pointer);
}
x_pointer address: 0x7fffba86ec48
y_pointer address: 0x7fffba86ec50

Change the address that x_pointer is pointing to and dereference it


#![allow(unused)]
fn main() {
x_pointer = y_pointer;
println!("\nx_pointer dereferenced: {}", x_pointer);
}
x_pointer dereferenced: 20

x_pointer and y_pointer now point to the same address known as a shared_reference

Scalar Types

Scalar = single values: integers, floating-point, booleans, characters

Integer Types

2's compliment representation

signed range: -(2n - 1) to 2n - 1

unsigned range: 0 to 2n - 1

LengthSignedUnsigned
8biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

Overflow logic


#![allow(unused)]
fn main() {
let x: u8 = 220;
let (x, ok) = x.overflowing_add(50);
if ok {
		panic!("Overflowing!")
}
println!("{}", x)
}

Floating points

IEEE-754 single precision and double precision (f32 and f64)

Char

Single quotes

4 Bytes - unicode scalar value

U+0000 to U+D7FF and U+E000 to U+10FFFF

Literals representations

Number literalsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'

String


#![allow(unused)]
fn main() {
let mut s = String::from("hello");
}

String::from requests the memory it needs

Deep copy to s2 (copy stack and heap data)


#![allow(unused)]
fn main() {
let s2 = s1.clone();
}

Create an array of bytes from the string


#![allow(unused)]
fn main() {
let bytes = s.as_bytes();
}

String Slice

String literals are exmaples of string slices &str, it is a pointer to a place in memory with a length. The literals point to a place in the binary.

A String can be dereference coerced to &str as it just takes a slice of the entire String.

A String in memory looks like this:

namevalue
ptraddress
len11
capacity11

Whereas a string slice &str looks like this:

Namevalue
ptraddress
len5

General Slice

General slices work the same way, it's just a pointer with a length


#![allow(unused)]
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];

println!("{:?}", slice)
}

Build a string with format!


#![allow(unused)]
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let word = format!("{}-{}-{}", s1, s2, s3);
}

Loop over string


#![allow(unused)]
fn main() {
let hello = "नमस्ते";
// Characters
for c in hello.chars() {
	println!("{}", c);
}
// Bytes
for b in hello.bytes() {
	println!("{}", b);
}
}

Multiline literals

Escape first newline with \


#![allow(unused)]
fn main() {
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
println!("{}", contents)
}

Struct

Define a struct type


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
}

Initialize with shorthand


#![allow(unused)]
fn main() {
fn build_user(email: String, username: String) -> User {
    User {
        username,
        email,
        sign_in_count: 1,
        active: true,
    }
}
}

Use update syntax


#![allow(unused)]
fn main() {
let user1 = build_user(String::from("jack@gmail.com"), String::from("jack"));
let user2 = User {
		email: String::from("newemail@gmail.com"),
		..user1
};
println!("{:#?}\n", user2);
}

Tuple struct


#![allow(unused)]
fn main() {
struct Point(i32, i32, i32);
let origin = Point(1, 25, 50);
}

Method on a struct

Mutate itself without taking ownership of itself


#![allow(unused)]
fn main() {
impl Rectangle {
    fn area(&mut self) {
        self.area = self.width * self.height;
    }
}
}

Mutate itself after taking ownership and then return itself

This is rare, could use it if transforming self into something very different, which would force the user to initialize it to another variable.


#![allow(unused)]
fn main() {
impl Rectangle {
    fn area(mut self) -> Rectangle {
        self.area = self.width * self.height;
        self
    }
}
}

Associated Functions

Function on a struct that doesn't use self, generallly return an instance of themselves e.g.


#![allow(unused)]
fn main() {
impl Rectangle {
	fn square(size: u32) -> Rectangle {
		Rectangle {
			width: size,
			height: size,
			area: 0,
		}
	}
}

let square = Rectangle::square(5);
}

Getter / Setter

This is an example of how you could implement OOP style getters and setters with checks.

#[derive(Debug)]
pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }

    pub fn value(&self) -> i32 {
        self.value
    }
}

fn main() {
    let guess = Guess::new(10);

    println!("{:?}", guess.value())
}

Enums

Standard

#[derive(Debug)]
enum IpAddrKind {
    V4,
    V6,
}

#[derive(Debug)]
struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };
    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };

    println!("{:?}", home)
}

Associated data

#[derive(Debug)]
enum IpAddr {
    V4(String),
    V6(String),
}

fn main() {
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));

    println!("{:?}{:?}", home, loopback)
}

Enum method

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        println!("self: {:?}", self);
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

Match logic with enum

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

impl Coin {
    fn value_in_cents(self) -> u8 {
        match self {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter => 25,
        }
    }
}

fn main() {
    let x = Coin::Dime;
    println!("{}", x.value_in_cents())
}

Enum inside enum, binds to match branch

#[derive(Debug)]
enum UsState {
    Alabambda,
    Alaska,
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

impl Coin {
    fn value_in_cents(self) -> u8 {
        match self {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter(x) => {
                println!("Comes from {:?}", x);
                25
            }
        }
    }
}

fn main() {
    let x = Coin::Quarter(UsState::Alaska);
    println!("{}", x.value_in_cents())
}

Option Some None enum match

This is how you check for a none / null /nil in Rust

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
        // _ matches all remaining values
        // _ => () will do nothing
    }
}

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

    println!("{:?}\n{:?}\n{:?}", five, six, none);
}

if let matching

if let is syntactic sugar for checking a match and running logic if true, then ignore all other values.


#![allow(unused)]
fn main() {
let x = Some(3);

// match on x if == Some(3), else do nothing
match x {
    Some(num) => println!("number: {}", num),
    _ => (),
}

// Exact same logic with if let
if let Some(num) = x {
    println!("number: {}", num)
// Can also throw in an else statement
} else {
    println!("not three");
}

}

Arrays

let a: [i32; 5] = [1, 2, 3, 4, 5];
// Set 5 ints of 5
let b = [5; 5];
println!("{:?}\n{:?}", a, b);

Tuples


#![allow(unused)]
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("{:?}", tup);
// Pretty print
println!("{:#?}", tup);
}

Destructure tuple


#![allow(unused)]
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}

Access item in tuple


#![allow(unused)]
fn main() {
let x = (10, 10.5);
let y = x.1;
println!("{}", y);
}

Conversions

String to int


#![allow(unused)]
fn main() {
let mut x = String::new();
stdin().read_line(&mut x).expect("Failed to read line");
let x: u32 = x.trim().parse().expect("Please type a number!");
}

int to String


#![allow(unused)]
fn main() {
let x: i32 = rand::thread_rng().gen_range(1..101);
let x = x.to_string();
}

#![allow(unused)]
fn main() {
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let mut scores: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
}

Vectors to HashMap


#![allow(unused)]
fn main() {
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let mut scores: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
}

Advanced Types

Newtype Definition

Simply wrapping a type in another to hide implementation details. e.g. you could wrap a HashMap<i32, String> in People and caller using the public API wouldn't need to know anything about the i32 ID.

Type Synonyms with Type Aliases


#![allow(unused)]
fn main() {
type Kilometers = u32;
let x: Kilometers = 6;
}

With a generic type


#![allow(unused)]
fn main() {
type Result<T> = std::result::Result<T, std::io::Error>;
}

Which would simplify the Write trait:


#![allow(unused)]
fn main() {
pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;

    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
}

Never Type

Denoted by !, it means the program never returns e.g. an endless loop or match branches that end with continue || panic

DST - Dynamically Sized Type

When using a generic function e.g.


#![allow(unused)]
fn main() {
fn generic<T>(t: T) {

}
}

Is actually treated like this:


#![allow(unused)]
fn main() {
fn generic<T: Sized>(t: T) {

}
}

Meaning it implements the Sized trait, so it must have a known size at compile to.

To relax this restriction:


#![allow(unused)]
fn main() {
fn generic<T: ?Sized>(t: &T) {

}
}

Functions

Statement vs Expression

statement - instruction that performs an action
expression - evaluate to resulting value

Function


#![allow(unused)]
fn main() {
fn add(a: i32, b: i32) -> i32{
    // No semicolon means it returns this value
    a + b 
}
let x = add(2, 5);
println!("x: {}", x);
``````output
x: 7
}

x: 7


x: 7

Block evaluation

Rust will consider opening and closing brackets a new scope, and allow you to return values from within.

fn main() {
    let y = {
        let x = 10;
        x + 1
    };
    println!("{}", y)
}

error: could not find Cargo.toml in /home/jacko/vimwiki/rust/src/basics or any parent directory


`x + 1` has no semicolon, and so it returns the value. If it had a semicolon it would be a statement and give you an error that it's returning `()` which can be thought of as an empty tuple denoting that nothing is returned.

Rust returns the last expression in a block or function implicitly.

Closures

Function definition compared to closures


#![allow(unused)]
fn main() {
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;
}

Test adding a header here


#![allow(unused)]
fn main() {
let x = vec![1, 5, 654, 78];
for item in x {
	println!("Nice one: {}", item)
}
}
Nice one: 1
Nice one: 5
Nice one: 654
Nice one: 78

Capturing value from environment

This is an example where the value from the surrounding environment is used in the closure.

fn main() {
    let x = 4;

    let equal_to_x = |z| x == z;

    let y = 4;
    println!("result: {}", equal_to_x(y));
}

Closure Traits

These are inferred from closure usage

  • FnOnce takes ownership of variable from surrounding environment, means the closure can't be called more than once
  • FnMut borrows mutably
  • Fn borrows immutably

move

Use the move keyword to ensure we transfer ownership to the closure, if it's a scalar value it will create a new value

fn main() {
    let x = 4;

    let equal_to_x = move |z| x == z;

    let y = 4;
    println!("result: {}", equal_to_x(y));
}

Closure use example

Create a cache to hold the result from a closure in a map

This allows to not run the same expensive function more than once for the same value

use std::collections::HashMap;
use std::hash::Hash;
use std::thread;
use std::time::Duration;
struct Cacher<T, U>
where
    T: Fn(U) -> U,
{
    calculation: T,
    values: HashMap<U, U>,
}

impl<T, U: Eq + Hash + Copy> Cacher<T, U>
where
    T: Fn(U) -> U,
{
    fn new(calculation: T) -> Cacher<T, U> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }
    fn values(&mut self, arg: U) -> U {
        if !self.values.contains_key(&arg) {
            self.values.insert(arg, (self.calculation)(arg));
        }
        self.values[&arg]
    }
}

fn generate_workout(intensity: u32, random_number: u32) {
    let closure = |num| {
        println!("calculating slowly....");
        thread::sleep(Duration::from_secs(2));
        num
    };
    let mut expensive_result = Cacher::new(closure);
    // Example only running expensive result closure when needed
    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.values("cool"));
        println!("Next, do {} situps!", expensive_result.values("cool"));
        println!("Next, do {} pullups!", expensive_result.values("very cool"));
    } else {
        // Example not having to run expensive calculation at all
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.values("so damn cool")
            )
        }
    }
}
fn main() {
    let intensity = 10;
    let simulated_random_number = 7;
    generate_workout(intensity, simulated_random_number);
}

Iterators

Basic iterator


#![allow(unused)]
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();

// This for loop makes v1_iter mutable
// As each iteration changes its internal state
for val in v1_iter {
	println!("Got: {}", val + 1)
}
}

What for does without syntactic sugar


#![allow(unused)]
fn main() {
let x = v1_iter.next();
println!("Got: {}", x.unwrap());
let x = v1_iter.next();
println!("Got: {}", x.unwrap());
let x = v1_iter.next();
println!("Got: {}", x.unwrap());


let x = v1_iter.next();
println!("Loop ended as x == {:?}", x.unwrap());
}

The x variables are immutable references, use into_iter for mutable references

Consuming Adaptors

Uses up an iterator by calling next(), an example is sum():


#![allow(unused)]
fn main() {
let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();

let total: i32 = v1_iter.sum();

println!("total: {}", 6);
}

Iterator Adaptors

Adds some functionality to each iteration


#![allow(unused)]
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter().map(|x| x * 2);

for x in v1_iter {
	println!("x: {}", x);
}
}

Iterator Adaptor Examples

Map


#![allow(unused)]
fn main() {
.map(|mut x| {
	x.style += "wow";
	x.size *= 10;
	x
}
}

Filter


#![allow(unused)]
fn main() {
.filter(|s| s.size == shoe_size)
}

Roll your own iterator


#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

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

// This is all we need to implement to get access
// to all `Iterator` methods
impl Iterator for Counter {
    type Item = u32;

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


// Example using a few `Iterator` methods
#[test]
fn using_other_iterator_trait_methods() {
    let sum: Vec<u32> = Counter::new()
        // Create pairs from iterator e.g. (1, 1), (2, 2)
        .zip(Counter::new())
        // Multiply the pairs together, which is squaring them
        .map(|(a, b)| a * b)
        // Filter out anything that isn't divisible by 3
        .filter(|x| x % 3 == 0)
        .collect();
    assert_eq!(sum, vec![9, 36, 81, 144, 225])
}
}

Function Pointers

Some languages refer to this ass first class functions i.e. functions can be passed around.

Example

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);
    println!("The answer is: {}", answer);
}
The answer is: 12

Function pointers implement all three closure traits: fn, FnMut and FnOnce, so it's best to write a generic parameter using one of those so you can use closures or functions.

Using function pointer in place of closure

Closure


#![allow(unused)]
fn main() {
let list_of_numbers = vec![12, 10, 4, 54];
let list_of_strings: Vec<String> = list_of_numbers.iter().map(|i| i.to_string()).collect();
println!("{list_of_strings:?}");
}
["12", "10", "4", "54"]

Function Pointer


#![allow(unused)]
fn main() {
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers.iter().map(ToString::to_string).collect();

println!("{list_of_strings:?}");
}
["1", "2", "3"]

Standard lib implements ToString for any type that implements Display

Passing initializers as functions

When you initialize a struct or an enum, you're actually calling a function that returns an instance that's constructed from the arguments. So you can pass an initialization to something accepting Fn or fn, for example below with Status::Value

#[derive(Debug)]
enum Status {
    Value(u32),
    Stop,
}

fn main() {
    let mut list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
    list_of_statuses.push(Status::Stop);
    println!("{:?}", list_of_statuses);
}
[Value(0), Value(1), Value(2), Value(3), Value(4), Value(5), Value(6), Value(7), Value(8), Value(9), Value(10), Value(11), Value(12), Value(13), Value(14), Value(15), Value(16), Value(17), Value(18), Value(19), Stop]

Return a closure

Must return a pointer with a dynamically sized Fn trait


#![allow(unused)]
fn main() {
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

let x = returns_closure();
let y = x(4);

println!("{y}");
}
5

Generics

struct method with generics


#![allow(unused)]
fn main() {
struct Point<T> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
	}
}
// Methods only available on f32's
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}
}

Inference working with mixed types

P3 knows that its a Point<i32, char>

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

Monomorphization

This is where generic types are replaced with concrete types at compile time.

e.g. if Option<T> is used with an i32 and a f64,


#![allow(unused)]
fn main() {
let integer = Some(5);
let float = Some(5.0);
println!("Wow: {} {}", integer, float);
}

At compile time the code will expand to:

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

Which removes the runtime penalty, but does cost in compile time.

Traits

Default implementation

NewsArticle implements Summary, but defines it's own version. Tweet uses the default implementation by using an empty block.


#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {}
}

Functions

Function implementing interface

Now we can set function that accepts anything which implements Summary


#![allow(unused)]
fn main() {
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
}

Long form

The trait bound function is using short form impl keyword, if we wanted to constrain two arguments to use the same type we'd need to use the long form:


#![allow(unused)]
fn main() {
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
}

Constrain to multiple traits:


#![allow(unused)]
fn main() {
// short form
pub fn notify(item: &(impl Summary + Display)) {}
// long form
pub fn notify<T: Summary + Display>(item: &T) {
}

Where clause

Implementing multiple interfaces across multiple arguments can become long


#![allow(unused)]
fn main() {
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
}

There is alternate syntax to make this clearer


#![allow(unused)]
fn main() {
fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{}
}

Return trait


#![allow(unused)]
fn main() {
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}
}

Default implementation calling

There is no problem for a default implementation to call a function that hasn't been implemented. This allows the user of a trait to only have to define a small amount of logic.


#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}
}

Conditionally implement methods

You can implement a method, only when the type has implemented a trait, the below is an example of a implementing cmp_display() when a the type being used inside Pair has implemented Display and PartialOrd


#![allow(unused)]
fn main() {
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}
}

Advanced

Placeholder Types (associated type)


#![allow(unused)]
fn main() {
pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
}

This can be used like:


#![allow(unused)]

fn main() {
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
}

Where a u32 is being returned

Operator Overloading

This example is similar in that there is an Output placeholder type that determines what is returned.

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

This is what the Add trait looks like:


#![allow(unused)]
fn main() {
trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}
}

Notice the Add<Rhs=Self>, this is called a Default Generic Type Parameter, below we will override the default of Self to add two different types:

use std::ops::Add;

#[derive(Debug)]
struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    println!("{:?}", Point { x: 1, y: 25 } + Point { x: 5, y: 10 });
    let x = Millimeters(2550);
    let y = Meters(3);
    println!("{:?}", x + y);
}

Fully Qualified Disambiguation

When there are multiple implementations of traits with the same method name, you need to fully qualify so Rust knows which one to use:

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    person.fly();
    Wizard::fly(&person);
    Pilot::fly(&person);
}

Note that this only works because &self is passed in, if it doesn't have &self i.e. there's no object that it's attached to, you have to use a different syntax:

trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

<Dog as Animal>::baby_name()) is refereed to as fully qualified syntax

The spec is:


#![allow(unused)]
fn main() {
<Type as Trait>::function(receiver_if_method, next_arg, ...);
}

Supertraits

Below we use self.to_string(), that method only appears as part of fmt::Display, which is added as a supertrait with OutlinePrint: fmt::Display. Now before OutlinePrint can be implemented on Point, we also need to implement fmt::Display

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

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

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl OutlinePrint for Point {}

fn main() {
    let x = Point { x: 25, y: 10 };
    x.outline_print();
}

Newtype Pattern with Traits

Use external traits with external types by wrapping it in a new type like so:

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[\n  {}\n]", self.0.join("\n  "))
    }
}

fn main() {
    let w = Wrapper(vec![
        String::from("hello"),
        String::from("world"),
        String::from("coolio"),
        String::from("coolio"),
    ]);
    println!("w = {}", w);
}

This allows us to override Display on the Vec type, if you then want to get all the methods on Vec<String> just override the deref:


#![allow(unused)]
fn main() {
impl Deref for Wrapper {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
}

Default Trait

This allows you to to set default's that offers some extra functionality:


#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Person {
    name: String,
    age: i32,
}
// We implement the Default trait to use it as a fallback
// when the provided string is not convertible into a Person object
impl Default for Person {
    fn default() -> Person {
        Person {
            name: String::from("John"),
            age: 30,
        }
    }
}
}

You can then fill in all the rest of the options with defaults:


#![allow(unused)]
fn main() {
let john = Person {
	age: 21,
	..Default::default()
};
println!("{john:?}")
}
Person { name: "John", age: 21 }

AsRef

Allows functions to take generic types that implement certain traits

e.g. str and String both implement AsRef<str>, so they can both be used this function where we know that they'll have access to .as_bytes()


#![allow(unused)]
fn main() {
fn byte_counter<T: AsRef<str>>(arg: T) -> usize {
    arg.as_ref().as_bytes().len()
}
}

Trait Objects dyn T

These allow for multiple concrete types to fill in for an object at runtime.

Example below is a user adding UI elements at runtime, we don't know what they'll add at compile time so we keep a Vector, as long as it implements Draw it can be added to the list. Then we can loop over each item and call draw()

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

pub trait Draw {
    fn draw(&self);
}

impl Draw for Button {
    fn draw(&self) {
        println!(
            "Drawing a Button with width: {} height: {} label: {}",
            self.width, self.height, self.label
        );
    }
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

#[derive(Debug)]
pub struct SelectBox {
    pub width: u32,
    pub height: u32,
    pub options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        println!(
            "Drawing a Button with width: {} height: {} options: {:?}",
            self.width, self.height, self.options
        )
    }
}

fn main() {
    let screen = Screen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No"),
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("Ok"),
            }),
        ],
    };

    screen.run();
}

Must be object safe, rules are the all methods must:

  • Not return Self.
  • Have no generic type parameters.

Lifetimes

Basic

'a denotes the lifetime, this tells the compiler that whatever comes in will share a lifetime with what goes out.


#![allow(unused)]
fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
}

This makes the return value valid for the smaller of the lifetimes of x and y.

Error

If any of the parameters (which are references) don't live as long as the return value, it will cause an error. Because that could be referencing a dangling pointer.


#![allow(unused)]
fn main() {
let string1 = String::from("long string is long");
let result;
{
    let string2 = String::from("xyz");
    result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is: {}", result);
}

Structs

#[derive(Debug)]
struct ImportantExcerpt<'a> {
    part: &'a str,
    author: &'a String,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let author;
    {
        author = String::from("Jacky C Yo");
    }
    let i = ImportantExcerpt {
        part: first_sentence,
        author: &author,
    };
    println!("{:?}", i);
}

Lifetime elision

Inference the compiler does on some lifetime patterns e.g.


#![allow(unused)]
fn main() {
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

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

    &s[..]
}
}

The compile is able to determine the signature of this is:


#![allow(unused)]
fn main() {
fn first_word<'a>(s: &'a str) -> &'a str{}
}

Because it's simply returning a reference that lives the same amount of time as the one it returns.

Parameters = input lifetimes Return values = output lifetimes

Lifetimes Inference rules:

  1. Each input parameters gets its own lifetime parameter e.g. a'
  2. If there is exactly one input lifetime parameter, the lifetime is assigned to all output parameters
  3. If self in method and multiple parameters, all output lifetime parameters become self

3rd rule example


#![allow(unused)]
fn main() {
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("{}{}", announcement, self.part);
        self.part
    }
}
}

Annotating the function with 'a is not required because it satisfies the lifetime inference rules.

Static

Tells the compile that the reference lives for the entire duration of the program.

E.g. all &str live inside the binary, so they already have the lifetime of 'static:


#![allow(unused)]
fn main() {
// Redundant, compiler already knows this is 'static
let s: &'static str = "I have a static lifetime.";
}

Example of generic type, trait and lifetime

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
    T: Display,
{
    println!("Announcement: {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let x = longest_with_an_announcement("Wow very cool", "Not cool", "You win!");
    println!("{}", x)
}

General

Ownership

  • Each value 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 is dropped

Copying

  • If you copy a variable that is an owner e.g. let x = y, y becomes invalidated now that x is the owner. In other langauges this might be called a shallow copy but in rust it is actually a move.

  • Types that implement a Copy trait, will retain the older variable when assigned to a new variable. Types with a drop trait can not implement a copy trait.

  • Tuples will copy if all items implement a Copy trait, a mix of Drop and Copy will result in just the stack data (pointer, len and capacity) being copied, and the previous variable being invalidated.

Function move vs copy

Here we see a String type that implements drop, it loses ownership so is invalidated when passed to a function. A type implementing copy doesn't have that problem.

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // s can no longer be used

    let i = 5;
    makes_copy(i);
    // i can still be used
    println!("{}", i);
}

fn takes_ownership(s: String) {
    println!("{}", s);
}

fn makes_copy(i: i32) {
    println!("{}", i);
}

Reassign string to regain ownership

fn main() {
    let s = String::from("hello");
    let s = takes_ownership(s);
    println!("{}", s);
}

fn takes_ownership(mut s: String) -> String {
    println!("{}", s);
    s.push_str(", world!");
    s
}

Return ownership with tuple

fn main() {
    let s1 = String::from("hello");
    let (s1, len) = calculate_length(s1);
    println!("The length of '{}' is {}.", s1, &len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)
}

Pass a reference to retain ownership

This is called borrowing, can't modify the borrowed reference

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);

    println!("length of {}: {}", s1, len)
}

fn calculate_length(s: &String) -> usize {
    s.len()
    // s isn't dropped because it doesn't have ownership
}

Mutate borrowed variable

Needs to be marked as mutable in the:

  • declaration
  • argument
  • parameter
fn main() {
    let mut s1 = String::from("hello");
    change_string(&mut s1);
    println!("result is: {}", s1)
}

fn change_string(s: &mut String) {
    s.push_str(", world!");
}

Only one mutable borrow at a time

Won't compile as having two references could cause data races, as soon as a mutable borrow is applied, even immutatble borrows can't be in the same scope.


#![allow(unused)]
fn main() {
let mut s = String::from("hello");
let c1 = &mut s;
let c2 = &s;
c1.push_str(" cool dude");
println!("{}", s)
}

Putting in a different scope makes it OK


#![allow(unused)]
fn main() {
let mut s = String::from("hello");
{
    let c1 = &mut s;
    c1.push_str(" cool dude");
}
// c1 is dropped here so the reference no longer exists
let c2 = &mut s;
c2.push_str(" sweet as cuz");
println!("{}", s)
}

Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used. This will compile no problem:


#![allow(unused)]
fn main() {
let mut s = String::from("hello");

let c1 = &mut s;
c1.push_str(" cool dude");
let c2 = &mut s;
println!("Wow this works fine after mutate: {}", c2);
let c3 = &mut s;
println!("And also after read: {}", c3)
}

Order of dropping

  1. Variables and arguments are dropped in reverse order why later value may reference earlier one
  2. Nested values are dropped in source code order why More intuitive

Other notes

Mut refs can be used like an owner, except the owner is responsible for dropping value, and so you can't move the value it's referencing unless you leave another value in its place.

Stack and Heap

Terms

Frame Allocated at the top of the stack when a function is called, contains all variables within a function and any arguments it takes. Once the function returns, its stack frame is reclaimed. This is directly tied to lifetimes, where you can't reference a value if it's in a frame that has been reclaimed.

Stack

LIFO

Stores value in the order it gets them, removes values in the opposite order.

Data must have a known fixed size

Example


#![allow(unused)]
fn main() {
let x = "I'm on the stack!";

Migrate to Dive
Api access request
Check submission from Marvin
Data De-identification - Loki 
}

Immutable fixed size, written directly into the exe

Heap

Unknown size at compile time, can grow and shrink at runtime.

Memory allocator finds empty spot that's big enough, marks it as being in use, and returns a pointer. This is called allocating.

Values will live until they're explicitly deallocated, which is useful if you need a value to live beyond the lifetime of a functions frame.

The primary mechanism for interacting with the heap is a Box::new(value)

If you forget to deallocate it will stick around forever, aka leaking memory. Sometimes you want this happen e.g. file configs, in this case Box::leak(value) gives back a `static reference.

Example


#![allow(unused)]
fn main() {
let mut s = String::from("I'm on the heap!");
}

Mutable can change at runtime so goes onto the heap

Memory Layout


#![allow(unused)]
fn main() {
let s1 = String::from("hello");
}

Stack

namevalue
ptraddress
len5
capacity5

Heap

indexvalue
0h
1e
2l
3l
4o

if we do


#![allow(unused)]
fn main() {
let s2 = s1;
}

We are copying the stack data, not the heap data. So we get back a pointer to the same heap data.

Static Memory

Resides in the programs binary code, this contains anything declared with static and other constant values such as &str. This means the value lives for the duration of the program, getting the lifetime of `static, anything pointing to one of these values also gets `static.

Variables

Terms

  • Place: a location that can hold a value on the stack, heap, registers etc.
  • Pointer: An address to a place
  • Variable: named value slot on the stack
  • Value: combination of type and an element from that types domain of values
  • Representation: Used for a type to turn a sequence of bytes to a value

Example 1

Using that terminology lets break down a simple program


#![allow(unused)]
fn main() {
let x: u32 = 10;
let px: &u32 = &x;
println!("x: {}", px);
}
x: 10

Create variable named x with value 10. The stack now has a place with a sequence of bytes that can be converted back into a value using the u32 types representation


#![allow(unused)]
fn main() {
let x: u32 = 10;
}

Create a variable named px that contains a value of type pointer to the memory address of the place where we created the value 10


#![allow(unused)]
fn main() {
let px = &x;
}

Dereference the variable named px to access the place, printing its value by using the u32 types representation to convert it from a sequence of bytes to a value


#![allow(unused)]
fn main() {
println!("x: {}", px);
}
x: 10

the println macro in rust will dereference any values that are pointers, so you can omit the * in *px. To print the actual address (the value of the pointer) try:


#![allow(unused)]
fn main() {
println!("x: {:p}", px);
}
x: 0x7ffdc852a9ac

Example 2

Important to clearly understand what the value is in a pointer type:


#![allow(unused)]
fn main() {
let x: u32 = 10;
let y: u32 = 20;
let px1 = &x;
// Set the `value` of px2 to an address pointing to the `place` in memory containing the value 10
let mut px2 = &x;
println!("px1 value: {:p} dereferenced: {}", px1, px1);
println!("px2 value: {:p} dereferenced: {}", px2, px2);
// Update the `value` of px2 to an address pointing to the `place` in memory containing the value 20
px2 = &y;
println!("px2 value: {:p} dereferenced: {}", px2, px2);
}
px1 value: 0x7ffd4e2ef4a8 dereferenced: 10
px2 value: 0x7ffd4e2ef4a8 dereferenced: 10
px2 value: 0x7ffd4e2ef4ac dereferenced: 20

The part where people get confused is that rust automatically dereferences a value on a lot of occasions, go over the previous code to convince yourself that px2's value is an address of type &32, not a u32.

Flows

Sometimes described as dependency lines, they track the lifetimes of values. Looking at a simple program:


#![allow(unused)]
fn main() {
// Create 1st flow
let mut x = 5;
//  Continue 1st flow
let y = &x;
// Create a 2nd flow
x = 10;
// Continues 1st flow, conflicting with 2nd flow
println!("{}", y);
}
warning: value assigned to `x` is never read
 --> src/main.rs:7:1
  |
7 | x = 43;
  | ^
  |
  = note: `#[warn(unused_assignments)]` on by default
  = help: maybe it is overwritten before being read?

error[E0506]: cannot assign to `x` because it is borrowed
 --> src/main.rs:7:1
  |
5 | let y = &x;
  |         -- borrow of `x` occurs here
6 | // Now there are two mutable flows. No error yet
7 | x = 43;
  | ^^^^^^ assignment to borrowed `x` occurs here
8 | // Th
9 | println!("{}", y);
  |                - borrow later used here

For more information about this error, try `rustc --explain E0506`.
warning: `output` (bin "output") generated 1 warning
error: could not compile `output` due to previous error; 1 warning emitted

There cannot be exclusive and shared use of a value at the same time. If the print statement was omitted, the compiler would detect that 1st flow wasn't used again, so it would compile:


#![allow(unused)]
fn main() {
let mut x = 5;
// y is never used again
let y = &x;
x = 10;
println!("2nd flow x: {}", x)
}
2nd flow x: 10

#![allow(unused)]
fn main() {
let mut x = 42;
let y = &mut x;
*y = 10;
println!("{}", y)
}
10

Smart Pointers

6.1 Box: Single owner, can be mutable

6.2 Rc: multiple immutable owners

6.3 RefCell: multiple owners, can be mutable, runtime checked

Smart Pointer - Box<T>

Only provides indirection from the stack to the heap, no other special capabilities. No performance overhead.

It implements the traits Deref and Drop which deallocates memory when all references are out of scope.

Use Cases

  • Type without known size at compile time
  • Large data transfer ownership without copying
  • Own a value that implements a specific trait

Allocate single i32 to heap

You wouldn't do this in practice, as it's best for small values to be on the stack


#![allow(unused)]
fn main() {
let b = Box::new(5);
println!("b = {}", b)
}

Construct Function - cons

Recursive data construct using pairs, where one item is a single value, and the other is the next item which also contains a pair, until the Nil value is hit.

It's not often a useful concept in Rust, but it's good example for a recursive type to show where a Box is useful

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};
fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

    println!("{:?}", list)
}

Rc Reference Counted Smart Pointer

Used for multiple ownership e.g. if multiple edges are pointing to a value in a graph.

Cons list with reference counting

Here a is referenced by b and c

#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));

    println!("{:?}\n{:?}\n{:?}", a, b, c);
}

Rc::clone(&a) doesn't create a deep copy, it only increments the reference count. It's the same as calling a.clone(), but most implementations create a deep copy, so by convention we distinguish it's coming from Rc.

Rc::strong_count shows the amount of references.

RefCell

Allows for interior mutability without making the value mutable to outside code.

Example of using a mock object for testing purposes, where we need to mutate the test object, but we still want to implementation to remain immutable.

pub trait Messenger {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;
        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: you are over your quota!");
        }
        if percentage_of_max >= 0.9 {
            self.messenger
                .send("Urgent warning: You've used up over 90% of your quota!")
        }
        if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You've used up over 75% of your quota")
        }
    }
}

fn main() {
    println!("Wow")
}

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

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.borrow_mut().push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(100);
        println!("Print work: {:?}", mock_messenger.sent_messages);

        assert_eq!(mock_messenger.sent_messages.borrow().len(), 3);
    }
}

A RefCell has the same rules as normal borrowing, where it can only have one mutable borrow or many immutable borrows. But if the rules are violated, the code will still compile but panic at runtime:


#![allow(unused)]
fn main() {
impl Messenger for MockMessenger {
	fn send(&self, message: &str) {
		let mut one_borrow = self.sent_messages.borrow_mut();
		let mut two_borrow = self.sent_messages.borrow_mut();

		one_borrow.push(String::from(message));
		two_borrow.push(String::from(message));
	}
}
}

This throws a panic: 'already borrowed: BorrowMutError'

Combining Rc and RefCell

This example shows how to keep multiple references to a value that we can still mutate

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::{cell::RefCell, rc::Rc};

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

Weak

To use a Weak<T> value you must call borrow() to check if it's still in scope. This avoids circular references, below is an example where it's useful when we want two way references so you can get to a parent from the child:

use std::borrow::{Borrow, BorrowMut};
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
    let b = leaf.parent.borrow().upgrade();

    if let Some(node) = b {
        println!("banch value: {}", node.value);
    }

	println!(
        "branch refs - strong: {} weak: {}",
        Rc::strong_count(&branch),
        Rc::weak_count(&branch)
    )
}

This gets the value of the branch from the child after checking it's still there by using upgrade()

The branch still goes out of scope after the main function ends because its strong reference count remains at 1

Custom Smart Pointer

Data structure similar to Box


#![allow(unused)]
fn main() {
struct MyBox<T>(T);

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

Implementing Deref trait


#![allow(unused)]
fn main() {
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
}

Now running the dereference operator will return what it's pointing to

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

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

Behind the scenes this is running: *(y.deref())

Pointer Traits

Deref Coercion

String implements its Deref trait by returning an &str, so it can be deref coerced:

fn hello(name: &str) {
    for x in name.chars() {
        println!("Hello: {}!", x);
    }
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

This is what String Deref looks like:


#![allow(unused)]
fn main() {
#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}
}

The type Target = str; is what allows the type coercion

Without deref coercion this would have to be written as:


#![allow(unused)]
fn main() {
hello(&(*m)[..]);
}

There is no runtime penalty for this, it is all taken care of at compile time.

Deref Coercion Cases

  1. From &T to &U when T: Deref<Target=U>
  2. From &mut T to &mut U when T: DerefMut<Target=U>
  3. From &mut T to &U when T: Deref<Target=U>

This means that any type can be coerced to the target, except an immutable can not be coerced to a mutable (this would break borrowing rules)

Drop

Printing once all references go out of scope


#![allow(unused)]
fn main() {
impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}
}

Trying to call this directly would result in an double free error. Instead if you need to call it early use drop(), which is automatically in scope from std::mem:drop

Smart Pointer Memory Leaks

You can create reference chains when using RefCell and Rc, below is an example where a references b and b references a

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}

impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    println!("b initial rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // overflow the stack
    println!("a next item = {:?}", a.tail());
}

Concurrency

Threads in rust are 1:1 with the operating system to maintain a smaller runtime, green threads require a larger runtime.

Threads

Most basic usage with a handle.join()


#![allow(unused)]
fn main() {
use std::thread;
use std::time::Duration;
let x = String::new();
let handle = thread::spawn(|| {
	for i in 1..10 {
		println! {"Spawned thread: {}", i}
		thread::sleep(Duration::from_millis(1));
	}
});

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

handle.join().expect("Something went wrong in thread");
println!("This will print after the thread has finished");
}
Main thread: 1
Spawned thread: 1
Main thread: 2
Spawned thread: 2
Main thread: 3
Spawned thread: 3
Main thread: 4
Spawned thread: 4
Spawned thread: 5
Spawned thread: 6
Spawned thread: 7
Spawned thread: 8
Spawned thread: 9
This will print after the thread has finished

Passing ownership to thread

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

Channels

Sharing data to the main thread from a spawned thread

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

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
        tx.send(String::from("cool")).unwrap();
    });

    loop {
        match rx.recv() {
            Ok(message) => println!("Message from spawned thread: {}", message),
            _ => break,
        }
    }
}
Message from spawned thread: hi
Message from spawned thread: cool

This can be changed to run like an iterator instead


#![allow(unused)]
fn main() {
for received in rx {
	println!("Got: {}", received);
}
}

Arc

Simple example of creating shared reference via Arc to access the same Vec<u32> across multiple threads.

use std::sync::Arc;
use std::thread::{self};

fn main() {
    let numbers: Vec<u32> = (0..100).collect();
    let shared_numbers = Arc::new(numbers); // fix
    let mut joinhandles = Vec::new();

    for offset in 0..8 {
        let child_numbers = shared_numbers.clone();
        joinhandles.push(thread::spawn(move || {
            let mut i = offset;
            let mut sum = 0;
            while i < child_numbers.len() {
                sum += child_numbers[i];
                i += 8;
            }
            println!("Sum of offset {} is {}", offset, sum);
        }));
    }
    for handle in joinhandles.into_iter() {
        handle.join().unwrap();
    }
}

Mutex and Arc

Example of using a Mutex<T> with a string, it's responsible for providing mutability and locking the value when already in use. Arc<T> is responsible for reference counting when a reference is shared across threads.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(String::new()));
    let mut handles = vec![];

    for i in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += &format!("Thread {} ", i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap()
    }

    println!("Result: {}", *counter.lock().unwrap());
}

Another example with sleep instead of handles

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

struct JobStatus {
    jobs_completed: u32,
}

fn main() {
    let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
    let status_shared = status.clone();
    thread::spawn(move || {
        for _ in 0..10 {
            thread::sleep(Duration::from_millis(250));
            status_shared.lock().unwrap().jobs_completed += 1;
        }
    });
    while status.lock().unwrap().jobs_completed < 10 {
        println!("waiting... ");
        thread::sleep(Duration::from_millis(500));
    }
}

Use threads to print to stdout in parallel


#![allow(unused)]
fn main() {
use std::io::{stdout, Write};
use std::time::Duration;

use rand::Rng;

fn do_work(name: String) {
    let mut rng = rand::thread_rng();
    for _ in 0..40 {
        std::thread::sleep(Duration::from_millis(rng.gen_range(0..=30)));
        print!("{}", name);
        stdout().flush().ok();
    }
}

let a = std::thread::spawn(|| do_work("a".into()));
let b = std::thread::spawn(|| do_work("b".into()));
a.join().unwrap();
b.join().unwrap();
println!();
}
babaabababbabbbaabaaabbbabaaabababaababaababbbaababaaababbabbababaabaababbaabbbb

Attributes

derive

Add Debug to allow the program to print the structure of custom types, enums and structs with {:?}


#![allow(unused)]
fn main() {
#[derive(Debug)]
}

Derivable traits

Debug

Required for assert_eq and sets what is printed when using println!("{:?}", customType

PartialEq

Determines what causes two types to be == or -= to each other, by setting the functions eq and ne

PartialOrd

Determines the order when comparing complex data types with > >= < <=.The order of structs and enums from top to bottom matches smallest to largest when deriving PartialOrd

Eq

Determines when two objects are equal to each other

cfg

Mark a function as a test so it doesn't produce warnings about unused use directives.


#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
	#[test]
	fn it_works() {
		assert_eq!(2 + 2, 4);
	}
}
}

PartialEq Example

This uses the derivable trait to set the default equality check PartialEq on the enum Size and then overwrites it on the struct Dog so only hair has to match for a Dog to be equal to another one.

#[derive(Debug, PartialEq)]
enum Size {
    Big,
    Small,
}

#[derive(Debug)]
struct Dog {
    hair: String,
    size: Size,
}

impl PartialEq for Dog {
    fn eq(&self, other: &Self) -> bool {
        self.hair == other.hair
    }
}

fn check_equal(a: &Dog, b: &Dog) -> bool {
    if a == b {
        return true;
    }
    false
}

fn main() {
    let ben = Dog {
        hair: String::from("Golden"),
        size: Size::Big,
    };

    let axel = Dog {
        hair: String::from("Golden"),
        size: Size::Small,
    };

    let eq = check_equal(&ben, &axel);
    println!("Are {:?} and {:?} equal: {}", ben, axel, eq);
}

PartialOrd example

#[derive(Debug, PartialEq, PartialOrd)]
enum Size {
    Small,
    Medium,
    Large,
}

#[derive(Debug, PartialEq, PartialOrd)]
struct Doggy {
    size: Size,
    breed: String,
}

fn main() {
    let lab = Doggy {
        breed: String::from("Lab"),
        size: Size::Large,
    };
    let pug = Doggy {
        breed: String::from("Pug"),
        size: Size::Small,
    };

    println!(
        "Is a {} bigger than a {}: {}",
        lab.breed,
        pug.breed,
        lab > pug
    );
}

Project Structure

General Notes

  • src/main.rs can't be tested so it should be minimal, move any complicated logic into src/lib.rs

Primitive Obsession

Anti-pattern e.g. returning a tuple and immediately destructuring it is a sign the code can be refactored into a struct e.g.


#![allow(unused)]
fn main() {
fn parse_config(args: &[String]) -> (&str, &str) {
    (&args[1], &args[2])
}
}

Would be better written as


#![allow(unused)]
fn main() {
struct Config {
    query: String,
    filename: String,
}

fn parse_config(args: &[String]) -> Config {
    Config{query: args[1].clone(), filename: args[2].clone()}
}
}

Struct::new()

It's idiomatic to create a struct using a new() method

pub struct Post {
    // Option - box + trait object
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        // Create a post
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }

    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }

    pub fn content(&self) -> &str {
        ""
    }

    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }

    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    fn request_review(self: Box<Self>) -> Box<dyn State>;
    fn approve(self: Box<Self>) -> Box<dyn State>;
    fn content<'a>(&self, _post: &'a Post) -> &'a str {
        ""
    }
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        Box::new(Published {})
    }
}

struct Published {}

impl State for Published {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}

fn main() {
    let mut post = Post::new();

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());

    post.request_review();
    assert_eq!("", post.content());

    post.approve();
    assert_eq!("I ate a salad for lunch today", post.content());
}

Patterns

Simple

A pattern in its most simple form:


#![allow(unused)]
fn main() {
let x = 5;
}

let PATTERN = EXPRESSION;

Destructure Pattern


#![allow(unused)]
fn main() {
let (x, y, z) = (1, 2, 3);
}

Ignore Pattern


#![allow(unused)]
fn main() {
let (x, ..) = (1, 2, 3);
// OR
let (x, _, _) = (1, 2, 3);
}

Destructure Struct

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

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

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

You could change the variable names like this:


#![allow(unused)]
fn main() {
let Point { x: a, y: b } = p;

println("{} {}", a, b);
}

Function pattern

Here &(x, y): &(i32, i32) is the pattern

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

Match values

With an or statement


#![allow(unused)]
fn main() {
let x = 1;

match x {
	1 | 2 => println!("one or two"),
	3 => println!("three"),
	_ => println!("anything"),
}
}

With a range


#![allow(unused)]
fn main() {
let x = 5;

match x {
	1..=5 => println!("one through five"),
	_ => println!("something else"),
}
}

Match with a destructured struct

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

impl Point {
    fn where_am_i(&self) {
        match self {
            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),
        }
    }
}

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

    let p = Point { x: 0, y: 7 };
    p.where_am_i();

    let p = Point { x: 8, y: 7 };
    p.where_am_i();
}

Destructure a struct inside an enum

enum Message {
    Move { x: i32, y: i32 },
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn print_me(&self) {
        match self {
            Message::Move { x, y } => {
                println!("Move in the x direction {} and in the y direction {}", x, y);
            }
            Message::ChangeColor(r, g, b) => {
                println!("Change the color to red {}, green {}, and blue {}", r, g, b)
            }
        }
    }
}

fn main() {
    let msg = Message::Move { x: 10, y: 15 };
    msg.print_me();

    let msg = Message::ChangeColor(0, 160, 255);
    msg.print_me();
}

Nested enum pattern matching

use Color::Hsv;
use Color::Rgb;
use Message::ChangeColor;
enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    ChangeColor(Color),
}

impl Message {
    fn print_me(&self) {
        match self {
            ChangeColor(Rgb(r, g, b)) => {
                println!("Change the color to red {}, green {}, and blue {}", r, g, b)
            }
            ChangeColor(Hsv(r, g, b)) => {
                println!( "Change the color to hue {}, saturation {}, and value {}", r, g, b)
            }
        }
    }
}

fn main() {
    let msg = ChangeColor(Rgb(25, 50, 50));
    msg.print_me();
    let msg = ChangeColor(Hsv(50, 45, 180));
    msg.print_me();
}

Ignoring

Underscore

Use the underscore to ignore certain patterns


#![allow(unused)]
fn main() {
let numbers = (2, 4, 8, 16, 32);

match numbers {
    (first, _, third, _, fifth) => {
        println!("Some numbers: {}, {}, {}", first, third, fifth)
    }
}
}

..

Use just the x value from a Point:


#![allow(unused)]
fn main() {
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),
}
}

Match guards

Adds an extra check to an arm


#![allow(unused)]
fn main() {
let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}
}

Check if the value of x matches 4 | 5 | 6, but y being true takes precedence:


#![allow(unused)]
fn main() {
let x = 4;
let y = false;

match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"),
}
}

Match Binding

@ gives access to variable which has been matched

enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => println!("Found an id in range: {}", id_variable),
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {}", id),
    }
}

Complex destructure

Making all values available through a destructure:


#![allow(unused)]
fn main() {
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}

Definitions

Dynamic Dispatch

Static Dispatch is when the compiler knows ahead of time what method it'll call.

An example of Dynamic Dispatch is trait objects e.g. Box<dyn Error>

Polymorphism

This is not inheritance, it just means code that can work with multiple data types. Rust uses generics and traits for this, which is sometimes referred to as bounded parametric polymorphism.

Test double

Type used that is used in place of another type during testing

Mock Object

Types of test doubles that record what happens during a test

Green threads

A thread that is different to operating system threads, where there may be more green threads.

Runtime

The code that a language ships in every binary, when people say no runtime they really mean small runtime unless it's assembly language

Duck typing

If it walks like a duck and quacks like a duck, then it must be a duck. Means that we don't need to know what the concrete type is at runtime, we just run a certain method on each type.

Refutable

This means the pattern may not match, it's used only in if let and while let. Irrefutable means the pattern will match or the program will fail to compile.

FFI

Foreign function interface defines usage of functions from a foreign programming language. This is done with extern in Rust

ABI

Application Binary Interface defines how the functions from another language are called at the assembly level.

Static Variable

Global variable with lifetime of \static`. Different to const in that they're in a fixed place in memory, consts duplicate their data wherever they're used.

Thunk

Code to be evaluated at a later time

DSTs

Dynamically sized types

General

URI

Uniform Resource Identifier

  • /
  • data:,Hello%20World
  • /data/banks
  • https://mysite.com/data/banks

A URI can be a URL, a URN or both, but doesn't have to be

URL

Uniform Resource Locator

  • http://mysite.com
  • https://mysite.com/data/banks
  • ftp://myfileserver.com

URN

Uniform Resource Name A URI that is unique across space and time e.g.

  • urn:oasis:names:specification:docbook:dtd:xml:4.1.2
  • urn:publishing:book

CR LF

Carriage Return, Line Feed - what DOS used when some devices needed a carriage return and some needed a line feed. Windows newlines still use \r\n while linux uses \n

Unsafe

Dereferencing a Raw Pointer

Immutable raw pointer: *const T mutable raw pointer: *mut T

Reasons why raw pointers are unsafe

  • Ignore borrowing rules e.g. multiple mutable pointers to the same location
  • Aren’t guaranteed to point to valid memory
  • Are allowed to be null
  • Don’t implement any automatic cleanup

Example


#![allow(unused)]
fn main() {
let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
	println!("r1: {}", *r1);
	*r2 = 50;
	println!("r2: {}", *r2);
}
}

Calling an Unsafe Function or Method


#![allow(unused)]
fn main() {
unsafe fn dangerous() {}

unsafe {
	dangerous();
}
}

Creating a safe abstraction on unsafe code

use core::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];

    let r = &mut v[..];

    let (a, b) = split_at_mut(&mut v, 3);

    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);
}

Calling functions from other languages

Makes abs from C available

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

Calling Rust function from another language

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

Once compiled to library can be linked to from C

Static Variables (globals)

You can modify global state in unsafe Rust

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

Unsafe Traits

Must use unsafe keyword to implement an unsafe trait

unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}

Unions

To access fields in a union is unsafe, as there is no guarantee what type it will be. Unions are generally only used for interacting with C code, they're like enums without the safety guarantees.

#[repr(C)]
union MyUnion {
    f1: u32,
    f2: f32,
}

fn main() {
    let mut u = MyUnion { f2: 1.5 };
    unsafe {
        let f = u.f2;
        println!("proper: {} wrong: {}", u.f2, u.f1);
        u.f1 = 10;
        println!("proper: {} wrong: {}", u.f1, u.f2);
    }
}

Speed up compiler

Incremental builds

Only do this locally, sccache won't play nice with it in CI

cargo.toml

[profile.release]
debug = 1
incremental = true

Declarative Macros

Aka

  • Macros by example
  • macro_rules!
  • macros

Simplified vec example

// indicates macro is in scope if crate is in scope
#[macro_export]
macro_rules! vec2 {
    // $()     = pattern to match
    // $x:expr = match any expression and name it $x
    // ,*      = one or more matches
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

fn main() {
    let v: Vec<u32> = vec2![1, 2, 3];
    println!("v = {:?}", v);
}

the block inside {$()*} results in the macro expanding, so it would become:


#![allow(unused)]
fn main() {
{
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
}
}

Procedural Macros

Generates code from attributes.

Code must live in its own special crate type.

Create a procedural macro


#![allow(unused)]
fn main() {
use proc_macro;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
}

Example

hello_macro_derive/Cargo.toml

[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0", features = ["extra-traits"] }
quote = "1.0"

hello_macro_derive/lib.rs


#![allow(unused)]
fn main() {
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
    // #name replaces value in println! below, it works with repetition
    impl HelloMacro for #name {
            fn hello_macro() {
                // Stringify turns an expression into a string literal
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };
    gen.into()
}
}

pancakes/Cargo.toml

[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hello_macro_derive = { path = "../hello_macro_derive" }

pancakes/main.rs

use hello_macro_derive::HelloMacro;

trait HelloMacro {
    fn hello_macro() {}
}

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}
Compiling output v0.0.1 (/tmp)
error[E0432]: unresolved import `hello_macro_derive`
 --> main.rs:4:5
  |
4 | use hello_macro_derive::HelloMacro;
  |     ^^^^^^^^^^^^^^^^^^ use of undeclared crate or module `hello_macro_derive`

error: cannot determine resolution for the derive macro `HelloMacro`
  --> main.rs:10:10
   |
10 | #[derive(HelloMacro)]
   |          ^^^^^^^^^^
   |
   = note: import resolution is stuck, try simplifying macro imports

error[E0599]: no function or associated item named `hello_macro` found for struct `Pancakes` in the current scope
  --> main.rs:14:15
   |
11 | struct Pancakes;
   | ---------------- function or associated item `hello_macro` not found for this
...
14 |     Pancakes::hello_macro();
   |               ^^^^^^^^^^^ function or associated item not found in `Pancakes`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
note: `HelloMacro` defines an item `hello_macro`, perhaps you need to implement it
  --> main.rs:6:1
   |
6  | trait HelloMacro {
   | ^^^^^^^^^^^^^^^^

Some errors have detailed explanations: E0432, E0599.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `output` due to 3 previous errors

#![allow(unused)]
fn main() {
println!("{}", x);
}
Compiling output v0.0.1 (/tmp)
error[E0425]: cannot find value `x` in this scope
 --> main.rs:4:16
  |
4 | println!("{}", x);
  |                ^ not found in this scope

For more information about this error, try `rustc --explain E0425`.
error: could not compile `output` due to previous error

#![allow(unused)]
fn main() {
use regex::Regex;
let x = 10;
println!("Wow nice");

let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();

}
Wow nice

#![allow(unused)]
fn main() {
use smallvec::{SmallVec, smallvec};
    
// This SmallVec can hold up to 4 items on the stack:
let mut v: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4];

// It will automatically move its contents to the heap if
// contains more than four items:
v.push(5);

// SmallVec points to a slice, so you can use normal slice
// indexing and other methods to access its contents:
v[0] = v[1] + v[2];
v.sort();

println!("{:?}", v);
}
[2, 3, 4, 5, 5]

#![allow(unused)]
fn main() {
use getrandom::getrandom;
fn get_random_buf() -> Result<[u8; 32], getrandom::Error> {
    let mut buf = [0u8; 32];
    getrandom::getrandom(&mut buf)?;
    Ok(buf)
}for x in vec![1, 2, 4, 5] {
	println!("Wow: {}", x);
}
}

Attribute Macros

These are the same as Procedural Macros except:

  • Allow you to create new attributes
  • Can be applied to more than just structs and enums
  • Don't use derive

Example


#![allow(unused)]
fn main() {
#[route(GET, "/")]
fn index(){}
}

This would be defined like


#![allow(unused)]
fn main() {
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
}

attr Takes in the GET, "/" part, and item takes in the body, which is the function in this case.

main.rs

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        handle_connection(&stream);
    }
    // Stream is mutable because it updates internal state after data has been read from it
    fn handle_connection(mut stream: &TcpStream) {
        let mut buffer = [0; 1028];
        let x = stream.read(&mut buffer).unwrap();
        println!(
            "\n---------------\nBytes read: {}\n---------------\n{}",
            x,
            String::from_utf8_lossy(&buffer[..])
        );
    }
}

TcpStream

This needs to be mutable because it keeps a track of its own state. When data is read to a buffer, it's removed from the stream. If there's not enough room in the buffer, it remains in the stream.

Response

If you navigate to "127.0.0.1:7878" it will return this response:

---------------
Bytes read: 558
---------------
GET / HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-GPC: 1
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

HTTP requests are in this format:

Method Request-URI HTTP-Version CRLF
headers CRLF
message-body

Rustup

Install (linux)

Arch package: rustup

OR

Script: curl https://sh.rustup.rs -sSf | sh

Install compiler and cargo

rustup toolchain install stable

Install nightly

rustup toolchain install nightly --allow-downgrade

Testing

Simple test


#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    fn is_larger() {
        assert!(larger(5, 10) == 10);
    }
}
}

Test types

All examples would pass


#![allow(unused)]
fn main() {
assert!(larger(5, 10) == 10);
assert_eq!(larger(5, 10), 10);
assert_ne!(larger(5, 10), 5);
}

assert_eq! and assert_ne! must implement PartialEq and Debug, so structs and enums need to be annotated with:


#![allow(unused)]
fn main() {
#[derive(PartialEq, Debug)]
}

Custom failures

Any arguments after the first assert! argument will be passed to format!, which will be passed to the panic message. This allows for more detail if a test fails.


#![allow(unused)]
fn main() {
pub fn greeting(name: &str) -> String {
    format!("Hello")
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        let name = "Carol";
        assert!(
            result.contains(name),
            "Greeting did not contain name: {}, returned value was: `{}`",
            result,
            name
        );
    }
}
}

Should panic

Gives a nice expected/got message if it fails, the expected parameter is optional for the should_panic attribute


#![allow(unused)]
fn main() {
pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess > 1 && < 100, got {}.", value);
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic(expected = "Guess > 1 && < 100, got 200")]
    fn greater_than_100() {
        Guess::new(200);
    }
}
}

Return a result

Hey silly, just return a Result!


#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}
}

Visibility

tests folder and documentation tests are compiled in separate binaries that have the same visibility as an external user of a library. To test private objects you must use embedded tests.

Cargo.md

Git dependency

// The version will go into the lock file, can update with `cargo update rand`
[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand.git"}

// with rev, not recommended
rand = { git = "https://github.com/rust-lang-nursery/rand.git", rev="9f35b8e"}

Make smaller binary

// This will stop cleanup when the program panics, leaving it to the OS
[profile.release]
panic = 'abort'

Add a feature from a crate

syn = { version = "1.0", features = ["extra-traits"] }

Change the default build target

Make a new file in the project root at: .cargo/config

[build]
target = "wasm32-unknown-unknown"

This will also update how rust analyzer reacts to

#[cfg(target_arch = "wasm32")]

Documentation

Cargo has inbuilt documentation tooling, also allowing tests inside the documentation so that it never becomes out of sync with the code base

Serve documentation in http

cargo doc --open

Use markdown inside comments

This assert will be tested with cargo test. Keeps documentation examples in sync with the library


#![allow(unused)]
fn main() {
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
}

The above comment applies tested html documentation to the add_one function built from markdown. So simple, so powerful.

Containing docstring

If this docstring is at the root of the crate, it will describe the entire crate


#![allow(unused)]
fn main() {
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
}

Debug

Trace back

To run with extra info about where panic originated outside of the calling code:

RUST_BACKTRACE=1 cargo run

For even more detailed info:

RUST_BACKTRACE=full cargo run

Debug symbols will be disabled in --release builds, but can be enabled manually.

Cargo Test

Help

Extra info about test options and attributes

cargo test -- --help

Options

Sets test threads to 1 so they don't interfere with each other

cargo test -- --test-threads=1

Allows successful tests to print output

cargo test -- --show-output

Filters on foo, any test that contains that pattern will run

cargo test foo

Any tests annotated with #[ignore] will run

cargo test -- --ignored

Commands

  • cargo build [--release | --debug]
  • cargo test Run through all test cases
  • cargo test foo Run through a single test case
  • cargo doc --open Create documentation and serve locally
  • cargo run Compiles debug build and runs it
  • cargo clean Removes target directories
  • cargo update Update all dependencies
  • cargo update -p rand Update a single crate

External commands

Install these external commands annotating the command name with cargo- e.g. cargo install cargo-watch

  • cargo watch -x "check" Run a cargo command every time some code changes
  • cargo expand Generate the output of macro code generation

Common Package Layout

├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   └── bin/
│       ├── named-executable.rs
│       ├── another-executable.rs
│       └── multi-file-executable/
│           ├── main.rs
│           └── some_module.rs
├── benches/
│   ├── large-input.rs
│   └── multi-file-bench/
│       ├── main.rs
│       └── bench_module.rs
├── examples/
│   ├── simple.rs
│   └── multi-file-example/
│       ├── main.rs
│       └── ex_module.rs
└── tests/
    ├── some-integration-tests.rs
    └── multi-file-test/
        ├── main.rs
        └── test_module.rs

Cargo Workspace

File Structure

Cargo.toml
Cargo.lock
release/
adder
	├── Cargo.toml
	├── src/
add-one
	├── Cargo.toml
	├── src/

Top level Cargo.toml

[workspace]
members = ["adder", "add-one"]

Depend on local crate in Cargo.toml

#adder/Cargo.toml

[dependencies]
add-one = { path = "../add-one" }

Special Workspace Commands

Test single package

cargo test -p add-one

#![allow(unused)]
fn main() {
println!("Wow cool")
println!("Wow cool")
println!("Wow cool")
}

Must publish each crate individually

Continuous Integration

CI Steps for rust

Tests

cargo test

Code Coverage

cargo install cargo-tarpaulin cargo tarpaulin --ignore-tests

Linting

  • cargo clippy
  • cargo clippy -- -D warnings fail linter check on warnings
  • #[allow(clippy::lint_name)] Ignore clippy on line

Formatting

cargo fmt -- --check

Security Vulnerabilities

cargo audit

Additional Checks

cargo deny identifies unmaintained crates, rejects unwanted licenses, spots reuse of crates with different version

Publishing

Cargo.toml minimum requirements

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2018"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

Publishing to crates.io

First create an account at crates.io

Then create an API key in the me page

And run commands:

cargo login [api_key]
cargo publish

egui

Simple Application With Eframe

Eframe allows you to target WebAssembly for a web application, and native applications with the same code, very cool! The bellow is the most simple implementation of an app.


#![allow(unused)]
fn main() {
use eframe::{egui, epi};

pub struct TemplateApp {
    label: String,
    value: f32,
}

impl Default for TemplateApp {
	fn default() -> Self {
		Self {
			// The values that will be mutable from the ui
            label: "Hello World!".to_owned(),
            value: 2.4,
        }
    }
}

// Epi is what hooks Egui into Eframe
impl epi::App for TemplateApp {
	// The name of the app in the native menu bar
    fn name(&self) -> &str {
        "Hello World App"
    }

	// Runs on first load
    fn setup(
        &mut self,
        _ctx: &egui::CtxRef,
        _frame: &mut epi::Frame<'_>,
        _storage: Option<&dyn epi::Storage>,
    ) {
    }

	// Runs on every frame like a game engine!
    fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
        let Self { label, value } = self;

		// Make a single panel with a heading and value slider
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading(label);
            ui.add(egui::Slider::new(value, 0.0..=100.0));
        });
    }
}

}

Change style example


#![allow(unused)]
fn main() {
let style = ui.style_mut();
style.spacing.item_spacing = egui::Vec2 { x: 10.0, y: 10.0 };
}

Eframe

Eframe allows you to target WebAssembly for a web application, and native applications with the same code.

Simple Application

use eframe::{egui, epi};

pub struct RustnoteApp {
    label: String,
    value: f32,
}

impl Default for RustnoteApp {
    fn default() -> Self {
        Self {
            // This state can be mutated
            label: "Hello World!".to_owned(),
            value: 2.4,
        }
    }
}

impl epi::App for RustnoteApp {
    fn name(&self) -> &str {
        // Native menu bar title
        "eframe template"
    }

    // This updates every on every frame
    fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
        let Self { label, value } = self;

        egui::CentralPanel::default().show(ctx, |ui| {
            // Edit the state on the struct
            ui.text_edit_singleline(label);
            ui.add(egui::Slider::new(value, 0.0..=100.0));
            // ui_counter(ui, counter);
        });
    }
}

// Only compiles when targeting native
#[cfg(not(target_arch = "wasm32"))]
fn main() {
    let app = rustnote::RustnoteApp::default();
    let native_options = eframe::NativeOptions::default();
    eframe::run_native(Box::new(app), native_options);
}

Where lib.rs has this for the web assembly compilation:


#![allow(unused)]
fn main() {
mod app;
pub use app::RustnoteApp;

// Only compiles when targeting web assembly, matches target arch
// e.g. the below with run on `cargo build --target wasm32-unknown-unknown`
#[cfg(target_arch = "wasm32")]
use eframe::wasm_bindgen::{self, prelude::*};
/// This is the entry-point for all the web-assembly.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
    let app = RustnoteApp::default();
    eframe::start_web(canvas_id, Box::new(app))
}
}

Persist State

Actix

Simple application

use actix_web::{web, App, HttpRequest, HttpServer, Responder};

async fn greet(req: HttpRequest) -> impl Responder {
    let name = req.match_info().get("name").unwrap_or("World");
    format!("Hello {}!", &name)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(greet))
            .route("/{name}", web::get().to(greet))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}

HttpServer

Listener for requests, maximum number of connections, TLS etc. All transport level concerns.

App

Where all the application logic lives, routing middleware, request handlers etc.

Main entry point to lib for easy testing

src/main.rs

use std::net::TcpListener;

use zero2prod::run;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind host");
    run(listener)?.await
}

lib.rs

src/lib.rs


#![allow(unused)]
fn main() {
pub fn run(listener: TcpListener) -> Result<Server, std::io::Error> {
    let server = HttpServer::new(|| {
        App::new()
            .route("/health_check", web::get().to(health_check))
    })
    .listen(listener)?
    .run();
    Ok(server)
}
}

Sqlx

Install for Postgres

cargo install --version=0.5.7 sqlx-cli --no-default-features --features postgres

Cargo.toml example for Postgres

[dependencies.sqlx]
version = "0.5.7"
default-features = false
features = [
	"runtime-actix-rustls",
	"macros",
	"postgres",
	"uuid",
	"chrono",
	"migrate",
]
  • runtime-actix-rustls use actix as runtime and rustls as TLS backend
  • macros gives access to sqlx::query and sqlx::query_as!
  • postgres makes all the postgres types available
  • uuid Allows generating uuid's from the uuid crate
  • chrono allows timestamptz to be mapped to DateTime<T>
  • migrate manage migrations from rust code

Environment variables

  • DATABASE_URL postgres://postgres:password@localhost:5432/newsletter

Command examples

  • sqlx database create create the database in the connection string
  • sqlx migrate add create_subscriptions_table: sqlx migrate add will create a sql file placeholder in migrations folder, add your script there:
-- Add migration script here
CREATE TABLE subscriptions(
	id uuid NOT NULL,
	PRIMARY KEY (id),
	email TEXT NOT NULL UNIQUE,
	name TEXT NOT NULL,
	subscribed_at timestamptz NOT NULL
);
  • sqlx migrate run run all the migrations in migrations folder

Config

Example


#![allow(unused)]
fn main() {
#[derive(serde::Deserialize)]
pub struct Settings {
    pub database: DatabaseSettings,
    pub application_port: u16,
}

#[derive(serde::Deserialize)]
pub struct DatabaseSettings {
    pub username: String,
    pub password: String,
    pub port: u16,
    pub host: String,
    pub database_name: String,
}

pub fn get_configuration() -> Result<Settings, config::ConfigError> {
    let mut settings = config::Config::default();
    settings.merge(config::File::with_name("configuration"))?;
    settings.try_into()
}
}

Where configuration.yaml looks like:

application_port: 8000
database:
  host: "localhost"
  port: 5432
  username: "postgres"
  password: "password"
  database_name: "newsletter"

HashMap


#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(1, 2);
map.insert(2, 3);
}

Loop

Iteration is in an arbitrary order


#![allow(unused)]
fn main() {
for (key, value) in &map {
    println!("{}: {}", key, value);
}
}

Insertion types

Insert

Overwrites if exists

Entry (do something if doesn't exists)


#![allow(unused)]
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// Doesn't do anything
scores.entry(String::from("Blue")).or_insert(20);
// Sets Yellow to 20
scores.entry(String::from("Yellow")).or_insert(20);
println!("{:?}", scores);
}

Update based on old value

Example of increasing word count based on how many times it's been seen

use std::collections::HashMap;
fn main() {
    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map)
}

Vector

Creating

Specify the type


#![allow(unused)]
fn main() {
let v: Vec<i32> = Vec::new();
}

Infer with first type pushed in

let mut v = Vec::new();
v.push(1);

Infer the type with a macro


#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
}

Get elements

Get element directly


#![allow(unused)]
fn main() {
let third: &i32 = &v[2];
}

Match option check

Do a check to see if element is there


#![allow(unused)]
fn main() {
match v.get(2) {
	Some(third) => println!("The third element is {}", third),
	None => println!("There is no third element."),
}
}

Loop over vector


#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
    for i in &mut v {
        *i += 50;
    }
}

Enum for different types

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

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

Match on the different types


#![allow(unused)]
fn main() {
 for x in row {
    match x {
        SpreadsheetCell::Int(x) => println!("{}", x),
        SpreadsheetCell::Text(x) => println!("{}", x),
        SpreadsheetCell::Float(x) => println!("{}", x),
    }
}
}

fs

Open file and write to it


#![allow(unused)]
fn main() {
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("helo.txt");
    let mut f = match f {
        Ok(file) => file,
        // Return early if there's an error
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    // Because last call it returns <String, io::Error>
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}
}

Read from a file the long way


#![allow(unused)]
fn main() {
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
}

Read from a file the short way


#![allow(unused)]
fn main() {
use std::fs::read_to_string;
let x = fs::read_to_string("hello.txt");
println!(x)
}

std env

Get environment variable


#![allow(unused)]
fn main() {
let editor = env::var("EDITOR").expect("EDITOR doesn't exist");
println!("{}", editor);
}

Linked List

Two Sum

use std::collections::HashMap;
pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
    let mut h: HashMap<i32, i32> = HashMap::new();
    for (i, v) in nums.iter().enumerate(){
        match h.get(&(target - v)) {
            Some(&i2) => return vec![i2, i as i32],
            None => h.insert(*v, i as i32),
        };
    }
    println!("{:?}", h);
    vec![0, 0]
}

fn main() {
   let result =two_sum(vec![1,5,6,7,8,15], 12);
   println!("{:?}", result);
}
pub fn length_of_longest_substring(s: String) -> i32 {
    let mut map = std::collections::HashMap::new();
    let mut start = 0;
    let mut result = 0;

    for (i, c) in s.chars().enumerate() {
        map.entry(c)
            .and_modify(|x| {
                start = start.max(*x + 1);
                *x = i;
            })
            .or_insert(i);
        result = result.max(i - start + 1);
    }
    result as i32
}

fn main() {
    let result = length_of_longest_substring(String::from("abcabcasbb"));
    println!("{}", result)
}