Notes are of


cargo run
  • Compiles then runs code

cargo build
  • Compiles code

  • --release enables all optimizations

cargo check
  • Quickly determines if the code will compile.


By default all variables are immutable, they can be made mutable by the mut keyword. Variables can be shadowed by using the let keyword. let also allows for the variable’s type to be overidden.

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters. You may recognize these from other programming languages. Let’s jump into how they work in Rust.

example of 32 bit

  • u32

  • i32

  • f32

Chars are unicode!

In summary:

  • const: An expression which can be evaluated where necessary

  • static: (barring optimizations) a location in global memory

  • let: (barring optimizations) a location in stack memory

  • Box/Arc: a managed pointer to heap memory


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


let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // [3, 3, 3, 3, 3]


const: An expression which can be evaluated where necessary static: (barring optimizations) a location in global memory let: (barring optimizations) a location in stack memory Box/Arc: a managed pointer to heap memory


  • Declared with fn

  • snake_case

  • pass by reference

fn another_function(x: i32, y: i32) {
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
  • an arrow is used for return type

fn plus_one(x: i32) -> i32 {
    x + 1


Unlike languages such as Ruby and JavaScript, Rust will not automatically try to convert non-Boolean types to a Boolean. You must be explicit and always provide if with a Boolean as its condition.

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

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


  • loop
    • infinite loop, no conditionals

  • for

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
  • while
    • infinite loop, conditional


Ownership is Rust’s most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.

  • Each value in Rust has a variable that’s called its owner.

  • There can only be one owner at a time.

  • When the owner goes out of scope, the value will be dropped.

see figure 4-4 for an example of moving

  • Clone for deep copy

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

println!("s1 = {}, s2 = {}", s1, s2);



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

    change(&mut s);

fn change(some_string: &mut String) {
    some_string.push_str(", world");

As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not simultaneous ones:

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

    let r1 = &mut s;

} // r1 goes out of scope here, so we can make a new reference with no problems.

et r2 = &mut s;

Mixing mutable and immutable references is also bad:

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

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

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

Scope is weird:

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

let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point (they go out of scope)

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

Copy types

Stack only data

  • All the integer types, such as u32.

  • The Boolean type, bool, with values true and false.

  • All the floating point types, such as f64.

  • The character type, char.

  • Tuples, if they only contain types that are also Copy. For example, (i32, i32) is Copy, but (i32, String) is not

Slice Type like python…

String != str
str == Slice of String

A more experienced Rustacean would write the signature shown in Listing 4-9 instead because it allows us to use the same function on both String values and &str values.

fn first_word(s: &str) -> &str {}


struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,

derived traits :


  • kinda like python, uses self

  • You don’t impliment methods in struct

  • impl

struct Rectangle {
    width: u32,
    height: u32,

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

        "The area of the rectangle is {} square pixels.",


  • Lists

  • Vectors

  • HashMaps


For the love of god, remember this

Some vs None

NULL but better


enum Coin {

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


litterally have no performance hits.

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;



Traits are similar to a feature often called interfaces in other languages, although with some differences. Think inhertable functions that multiple structs can impliment. Trait implementations can only done on a type only if either the trait or the type is local to the current crate.

Definition Example

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

Implimenting a Trait on a Type

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.location)

Default Implementations

Just like functional overriding in C++. Anything that is implimenting a trait can overide these default values

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

Note that it isn’t possible to call the default implementation from an overriding implementation of that same method.

Traits as parameters

This may be more powerful than generics! You can have funtions take in any type that implements a certain trait!

pub fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());

To force the type of multiple parameters to be the same generics are needed so that a single type is used for both parameters.

pub fn notify<T: Summary>(item1: T, item2: T) {

Binding Multiple Traits

See the book here for the basic details. When dealing with multiple generics use the where notation to make which generics use what traits.

fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
        U: Clone + Debug
{ /* Function goes here */}


The main aim of lifetimes is to prevent dangling references. See the lifetime docs for more info.

Error Propegation


When the panic! macro executes, your program will print a failure message, unwind and clean up the stack, and then quit. It is only to be used when the situation is not recoverable.


unwrap and expect

Propegation with ?

The ? operator placed after a Result. If the value of the Result is an Ok, the value inside the Ok will get returned from this expression, and the program will continue. If the value is an Err, the Err will be returned from the whole function as if we had used the return keyword so the error value gets propagated to the calling code.

Macros, Shit Just Got Real



by using super at the start of the path. This is like starting a filesystem path with the .. syntax

TODO: pub use

Automated testing

To change a function into a test function, add #[test] on the line before fn. To run the test cargo test. Benchmarking is avaliable

Librarys to Know about

  • File IO std::fs::File;

  • Stdin std::io

fun crates to know about.


String::split() returns a non-mutatble iterator and cannot be used to provide &mut str types

FStrings! format!("{}: {}", self.username, self.content)