Skip to content

arn-the-long-beard/ntnu_rust_lecture

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rust Lecture

rust_meme

Summary


History

C is ancient 📜

C_haters

https://www.youtube.com/embed/Fm5Ust7vEhk

#include <stdio.h>
int main() {
printf("Hello, World!");
return 0;
}

C & C++ use manual memory management

Example of memory management

// C++ Program to store GPA of n number of students and display it
// where n is the number of students entered by the user

#include <iostream>
using namespace std;

int main() {
    int num;
    cout << "Enter total number of students: ";
    cin >> num;
    float* ptr;

    // memory allocation of num number of floats
    ptr = new float[num];

    cout << "Enter grades of students." << endl;
    for (int i = 0; i < num; ++i) {
        cout << "Student" << i + 1 << ": ";
        cin >> *(ptr + i);
    }

    cout << "\nDisplaying GPA of students." << endl;
    for (int i = 0; i < num; ++i) {
        cout << "Student" << i + 1 << " :" << *(ptr + i) << endl;
    }

    // ptr memory is released
    delete[] ptr;

    return 0;
}

C++ and its challenges

  • Then garbage collector language like Java, C#, python to name few of them

What is garbage collector

duality

  • Golang came in the game

Is the memory management challenge solved ?

Not really

If it was solved with GC, why still so much use of C & C++ now.

We can ask Microsoft

Garbage collection in .Net

jvm_exploit

Did Java solved it ?

Bug with GC in Chrome

Garbage collector limitation : discord

  • Then a new challenger arrived, with releases in 2015, 2018 and 2021.

Languages in meme


Rust Intro

Discord, Npm, Delivroo,


Features

Memory safe

-> Ownership that allows you to:

a. Mutate on object/variable so you can write with mut

b. Borrow an object so you can Read its value with&

c. Own an object in an scope and manages its memory automatically, just pass it as assignment or function

d. Makes the compiler checks everything for you, it will be your best enemy first and then your best friend later 😁

Examples

a. Mutability & Immutability

struct Company {
  pub name: String,
  /// In Billion
  pub value: u32,
}

fn main() {
  let mut facebook = Company {
    name: "FaceMash".to_string(),
    value: 0,
  };
  
  // <- if we do not use mut here, compiler will say NO
  facebook.name = "Facebook".to_string();
  facebook.value = 900;

  println!("{} new name is cool ", facebook.name);
}

b. Borrowing data to read it.

fn display_data(company: &Company) {
  println!("Name : {} ", company.name);
  println!("Market Cap : {} ", company.name);
  println!("Rating {}", rate_business(&company.value))
}

fn main() {
    // ---- Rest of the code
  // 2 - Reference with `&` to read data
  // <- if we do not use & here, compiler will say NO because function asks for it.
  display_data(&facebook);
}

c. Owning an object to move it ( to consume it ) to do stuff with it.

fn rebuild_business(_: Company) -> Company {
  Company {
    name: "Facebook 2.0".to_string(),
    value: 850,
  }
}


fn main() {
  
  // ---- Rest of the code
  println!("{} is super old, we need rebranding ", facebook.name);


  facebook.name = "Meta".to_string();
  println!("{} is an awesome name", facebook.name);

  // <-- we move `facebook` inside the scope of the `rebuild_business` function so we cannot access it anymore .
  let mut new_facebook = rebuild_business(facebook);

  // println!("{} is still alive ?", facebook.name); <-- get moved error value
  display_data(&new_facebook);
  
}

d. Compiler check for everything for you

Little surprise with mutable references 😁

fn update_name(company: &mut Company, new_name: &str) {
  company.name = new_name.to_string();
}


fn main (){

  // --- Rest of the code ---
  let update = &mut new_facebook; // Can only make a single mutable reference.

  // display_data(&new_facebook); Cannot read while writing 😁
  // let update2 = &mut new_facebook;  throw error here because we can only have mutable reference at the same time.
  update.name = "Facebook 3.0".to_string();
  println!("{} is an awesome name.", new_facebook.name);

  let update2 = &mut new_facebook;
  update2.name = "Facebook 4.0".to_string();

  // <- Update name without taking ownership with function
  update_name(&mut new_facebook, "Facebook 3000");
  println!("{} is an awesome name.", new_facebook.name);
}

Resume

Pros :

  • Rules to write and read are easy
  • No extra syntax for these rules ( like Malloc or delete)
  • Everything is actually automated ( not need to delete object or do memory stuff on basic levels)
  • The Compiler checks everything for you
  • No need to think about technical detail so you can focus on business logic.

Cons :

  • Redefine the way you write code because you need to turn upside down your brain
  • You will hate the compiler
  • Get addicted to the safety

Is memory safety solved?

There are bugs You can do manual memory management if you want in Unsafe mode

Rust laughing at you


Traits

Inheritance vs trait compositions

  • Useful to have for objects that share specific behaviors

  • Flexible and allow having many types handled together

  • Can be understood as interfaces in some other languages

Examples

Got the idea to simulate the weapons system from Skyrim and how damages are dealt based on the stuff

https://en.uesp.net/wiki/Skyrim:Block#Defensive_Blocking

####### 1 - How to define weapons and armor

####### 2 - How to define a character

####### 3 - How to define a fight

As you will see , I took some freedom from their documentation for the calculation.

NB :

  • Mainly kept the rules for blocking for dual_wielding, two hands weapons, shield + single weapon and one single weapon only.

  • Coding with the flow, no much thinking because I had tons of fun

1 - Armor & Weapons
// Every "object ( weapons or armor is an item with a name )
pub trait Item {
  fn name(&self) -> &str;
  fn set_name(self, name: &str) -> Self
    where
            Self: Sized;
}

// We have some Armor
pub trait Armor: Item {
  fn set_armor_rating(self, armor_rating: ArmorRating) -> Self
    where
            Self: Sized;
  fn armor_rating(&self) -> &ArmorRating;
}

pub trait Weapon: Item {
  /// Describe how much damage a weapon can deal.
  /// More damage a weapon deals, better quality it is .
  fn damages(&self) -> &RawDamages;
  fn set_damages(self, amount: RawDamages) -> Self
    where
            Self: Sized;
  // Block attack and make calculation if possible
  fn can_block_if_possible(&self) -> Option<BlockedDamages> {
    match self.handheld_type() {
      HandheldType::SingleHand => Some(self.damages() * 0.4),
      HandheldType::TwoHands => Some(self.damages() * 0.7),
      // A bit dummy here because we have different implementation later.
      HandheldType::OnlyLeft => None,
    }
  }
  fn set_handheld_type(self, handheld: HandheldType) -> Self
    where
            Self: Sized;
  fn handheld_type(&self) -> &HandheldType;
}

// Can make alias type for better semantic.
pub type BlockedDamages = f32;
pub type RawDamages = f32;
pub type ArmorRating = f32;

-> Now we can make some struct that implement this trait.

Here is the shield which is a weapon, but it also has armor properties.

// ----- Rest of the Code
pub struct Shield {
    armor_rating: f32,
    name: String,
    hold: HandheldType,
    bash_damage: RawDamages,
    handheld: HandheldType,
}

impl Default for Shield {
    fn default() -> Self {
        Shield {
            armor_rating: 0.0,
            name: "".to_string(),
            hold: HandheldType::OnlyLeft,
            bash_damage: 0.0,
            handheld: HandheldType::OnlyLeft,
        }
    }
}

impl Armor for Shield {
    fn set_armor_rating(mut self, reduction: f32) -> Self {
        self.armor_rating = reduction;
        self
    }

    fn armor_rating(&self) -> &f32 {
        &self.armor_rating
    }
}

impl Item for Shield {
    fn name(&self) -> &str {
        &self.name
    }

    fn set_name(mut self, name: &str) -> Self {
        self.name = name.to_string();
        self
    }
}

impl Weapon for Shield {
    fn damages(&self) -> &RawDamages {
        &self.bash_damage
    }

    fn set_damages(mut self, amount: RawDamages) -> Self {
        self.bash_damage = amount;
        self
    }

    fn can_block_if_possible(&self) -> Option<BlockedDamages> {
        //We could have skills here to help us to calculate
        Some(self.armor_rating)
    }

    fn set_handheld_type(mut self, handheld: HandheldType) -> Self {
        self.handheld = handheld;
        self
    }

    fn handheld_type(&self) -> &HandheldType {
        &self.handheld
    }
}

// NB: I could have made multiple trait instead of enum as well.
#[derive(PartialEq)]
pub enum HandheldType {
  SingleHand,
  OnlyLeft,
  TwoHands,
}

impl Shield {
    pub fn new(name: &str, armor: f32, bash_damages: f32) -> Self {
        Self::default()
            .set_name(name)
            .set_armor_rating(armor)
            .set_handheld_type(HandheldType::OnlyLeft)
            .set_damages(bash_damages)
    }
}

Check the code for more examples.

Character definition and Stuff definition that qualifies the weapons + armor configuration.

2 - Character
pub struct Character {
    name: String,
    health: f32,
    max_health: f32,
    stuff: Stuff,
}

/// Here we store any kind of weapons and armor.
/// Stuff contains specific pointers to dynamic object
/// The compiler will say No to this until you tell him that the object size is know at compile time in the trait definition.
/// That is why we have the word `Sized` for self in previous trait.
#[derive(Default)]
pub struct Stuff {
    armor: Option<Rc<dyn Armor>>,
    first_weapon: Option<Rc<dyn Weapon>>,
    second_weapon: Option<Rc<dyn Weapon>>,  // Rc is a specific reference pointer
}

impl Stuff {
  
// Many other methods  
/// Will panic if you have equipped a two hand weapon as a second Weapon.
///  
pub fn equip_weapon<W: 'static + Weapon>(mut self, weapon: W) -> Self {
  match weapon.handheld_type() {
      
    HandheldType::SingleHand => {
      if let Some(current_weapon) = self.first_weapon() {
        if current_weapon.handheld_type() == &HandheldType::SingleHand {
          self.second_weapon = Some(current_weapon.clone())
        }
      }
      self.set_first_weapon(weapon);
    }
      
    HandheldType::OnlyLeft => {
      if let Some(current_first_weapon) = self.first_weapon() {
        if current_first_weapon.handheld_type() == &HandheldType::TwoHands {
          self.unset_first_weapon();
        }
      }

      // See comment on how we could avoid this issue at compile time.
      // if First weapon is set or not, we do not care, left item always goes left.
      if let Some(current_second_weapon) = self.second_weapon() {
        if current_second_weapon.handheld_type() == &HandheldType::TwoHands {
          panic!("It seems you have a two hand weapon as second weapon");
        }
      }

      self.set_second_weapon(weapon)
    }
    HandheldType::TwoHands => {
      self.unset_second_weapon();
      self.set_first_weapon(weapon);
    }
  }
  self
}

}
3 - Fight
pub struct Fight {
    winner_name: Option<String>,
    round: u16,
    opponents: (Character, Character),
}

#[allow(unused)]
impl Fight {
    pub fn winner_name(&self) -> &Option<String> {
        &self.winner_name
    }
    pub fn round(&self) -> u16 {
        self.round
    }
    pub fn opponents(&self) -> &(Character, Character) {
        &self.opponents
    }
    pub fn new(first_fighter: Character, second_fighter: Character) -> Self {
      Fight {
        winner_name: None,
        round: 0,
        opponents: (first_fighter, second_fighter),
      }
    }

    pub fn start(&mut self) {
        // My ugly code you can check
    }
}

Here is how the "game" looks like.

mod character;
mod dice;
mod fight;
mod item;
mod stuff;
use crate::character::Character;
use crate::fight::Fight;
use item::*;

fn main() {
  println!("Hello and Fight");

  // Lets put some armors.
  let iron_plate = BodyArmor::new("Iron Plate", 32.0);
  let steel_plate = BodyArmor::new("Steel Plate", 54.0);
  let daedric_armor = BodyArmor::new("Daedric Armor", 25.0);
  let daedric_armor_2 = BodyArmor::new("Daedric Armor 2", 25.0);

  // Lets put some shields
  let steel_shield = Shield::new("steal Shield", 55.0, 20.0);
  let iron_shield = Shield::new("Iron Shield", 25.0, 15.0);

  // Lets put some weapons.
  let iron_long_sword = RegularWeapon::new("Iron Long Sword", 35.0, HandheldType::SingleHand);
  let steel_battle_axe = RegularWeapon::new("Steel battle Axe", 65.0, HandheldType::TwoHands);
  let daedric_battle_axe = RegularWeapon::new("Daedric battle Axe", 85.0, HandheldType::TwoHands);

  let grand_ma_skyrim = Character::new("Skyrim Grandma", 300.00)
          .grab_weapon(steel_battle_axe)
          .grab_armor(daedric_armor);

  let white_run_guard = Character::new("Olaf the dummy guard", 300.00)
          .grab_weapon(steel_shield) // <- we can do it because of generic + trait objects for weapon
          .grab_weapon(iron_long_sword)
          .grab_armor(daedric_armor_2);

  Fight::new(white_run_guard, grand_ma_skyrim).start();
}
Resume

Pros:

  • We can make many object with different types and handle them as long as they have the behavior we need.
  • Can make default behavior
  • We can store objects without specific type as long as their size is safe = we know their size at compile time
  • Everything is always checked by our lovely compiler
  • Can back up stuff with unit-test

Cons:

  • Require some training to understand Generics and trait object
  • Need to use specific new pointer like Rc/Arc for advanced stuff
  • The compiler will be painful with you

Little Britain


Conditional compilation

Well, you can decide what to compile or not 😁

Example
  • Running tests
#[cfg(test)]
mod test {
    
    // --- unit test 
}

cargo test

The command will compile unit test and run them.

  • Having specific features

In cargo.toml

[features]
song=[]

Now in your code

#[cfg(feature = "song")]
fn add_song() {
    println!("Here is the song of the Dovakin")
}

We add the function in main as well 😁

fn main() {
    // ------ Rest of the code
    #[cfg(feature = "song")]
        add_song();
}

How to run :

cargo run --features song

Resume

Pros:

  • You decide what you want to compile
  • Super useful to reduce the size of your package and use only what you need

Cons:

  • Use strings, so little helping from the IDE ( just a bit still 😁)

Good example to use for this is web_sys library as bridge between Rust and the Web

feeling


Multithreading

Multiple concurrency models

Example

Let's make 2 fights simultaneously and take the winner for the last one.

    // ----- Rest of the Code

    let (tx_1, rx_1) = mpsc::channel();
    // This is OS native Thread
    let _ = thread::spawn(move || {
        let winner = Fight::new(white_run_guard, grand_ma_skyrim).resolve();
        tx_1.send(winner)
            .expect("Should have passed the resolved winner");
    });

    let (tx_2, rx_2) = mpsc::channel();

    // This is OS native Thread
    let _ = thread::spawn(move || {
        let winner = Fight::new(lydia, dovakin).resolve();
        tx_2.send(winner)
            .expect("Should have passed the resolved winner");
    });

    let second_fight_winner = rx_2.recv().expect("Should have receive the winner");
    let first_fight_winner = rx_1.recv().expect("Should have receive the winner");

    let final_winner = Fight::new(first_fight_winner, second_fight_winner).resolve();

    println!("The best fighter is : {}", final_winner.name());

Works like a charm. Of Course if my business logic sucks, then it won't work as expected, but that is not the compiler responsibility. But unit-test are there for it 😁

Resume

Pros:

  • Took me 5 minutes to make it work
  • No runtime bugs, if you do something wrong, the compiler will tell you 😁
  • You can make it simple and easy to understand

Cons:

  • The compiler complained about the Type for Stuff because I needed to add trait bounds to ensure the code was safe ( but the compiler told me again soo 😀)
  • Need to think the specific pointer you use such as Rc vs Arc ( but again the compiler helps with that )

c_rust_meme


Popularity

Discord switching from go to RUst

Why Linux, Android and Windows are switching to Rust now.

Google starts using it for Android

Aws

Microsoft and Rust

Linux & Rust

Rust used for web

Rust game engine

OMG performances

Simple Benchmark

More Complex Benchmark


Play

Explore opportunities


Conclusion

  • Can be used everywhere

  • Great Performance

  • Your code is bug free easy

  • Concurrency is easy

  • Super community

  • Made to change the world

  • Can solve most of modern tech challenges

Rust Discord

Ringrev Discord

learning curve with rust

More meme stuff

Thank you for your time :)

About

First lecture at NTNU Ålesund

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published