My Notes

My notes following Easy Rust tutorial.

  • https://github.com/Dhghomon
  • https://dhghomon.github.io/
  • https://www.youtube.com/watch?v=-lYeJeQ11OI&list=PLfllocyHVgsRwLkTAhG0E-2QxCf-ozBkk

Primitive


signed integers i8, i16, i32, i64, i128, and isize.

Unsigned integers u8, u16, u32, u64, u128, and usize.

πŸš€οΈ isize = 64 bit if possible if not, 32 bits

πŸš€οΈ If we dont choose an integer type rust will will choose i32

let my_number = 10  //32 Rust default

let number: u16 = 2  //u16;
let number = 2_u16   //u16;

Chars

πŸš€οΈThe char type represents a single character

πŸš€οΈA char is a β€˜Unicode scalar value’

πŸš€οΈWe use single quote to represent a char let temp = 'a';


Floats

πŸš€ f32 f64 πŸš€ rust will choose f64 by default

let my _float = 5.5; //f64

let float1 = 5.0;          // this is a f64
let float2: f32 = 8.5     // this is a f32
let total= float1 + float2  //πŸ›‘πŸ›‘ WRONG we are mixing two types this will not compile

Semicolons

Almost everything in Rust is an expression. An expression is something that returns a value. If you put a semicolon you are suppressing the result of this expression.

If you end your function with an expression without a semicolon, the result of this last expression will be returned

// here a = 4
let a = {
    let inner = 2;
    inner * inner
};

➑️ skinny arrow -> what the function is returning

// 8 without the semicolons means return 8
fn number () -> i32 {
    8
}

fn main () {
    println!("the number is {}",number());
}
// this will return ()
fn number(){
    8;
}

Debug Print

fn main(){
    let my_number = {
        second_number = 8;
        second_number + 7;
    }
    println!("{}" , second_number); // πŸ›‘ This is an error. because my_number will return ()
}

with this code block we will have two issues.


#![allow(unused)]
fn main() {
println!("{}" , second_number);
//                ^^^^^^^^^^^^ `()` cannot be formatted with the default formatter
//the trait `std::fmt::Display` is not implemented for `()`
}

the trait std::fmt::Display is not implemented for ()

that means () does not have the power of 'Display'

πŸš€ Trait = power for types

We have to use debug printing {:?}

or pretty debug printing {:#?}

fn main(){
    let my_number = {
        let second_number = 8;
        second_number + 7;
    };
    println!("output is: {:?}" , my_number); // => output is: ()
}

Mutability

vars are either

  • mutable => can be changed
  • immutable => can't be changed

πŸš€ vars are immutable by default.

fn main(){
    let my_number = 8;
    println!(" My number is: {}", my_number);

    my_number = 10; // πŸ›‘ we can't vars are immutable by default
    println!("My altered number is: {}" , my_number);
}

πŸš€ To make a var mutable we use the "mut" word.

fn main() {
    let mut my_number = 8;
    println!(" My number is: {}", my_number);

    my_number = 10; // βœ…
    println!("My altered number is: {}" , my_number);
}

Shadowing

fn main(){
    let my_number = 8;
    println!(" My number is: {}", my_number);

    let my_number = 9.2; // βœ”οΈ This is a new var with  a f64 type !!
    println!("My altered number is: {}" , my_number);
}

πŸš€ Shadowing will not kill the first var! it only block it ...

fn main() {
    let my_number = 8; // This is an i32
    println!("{}", my_number); // prints 8
    {
        let my_number = 9.2; // This is an f64. It is not my_number - it is completely different!
        println!("{}", my_number) // Prints 9.2
                                  // But the shadowed my_number only lives until here.
                                  // The first my_number is still alive!
    }
    println!("{}", my_number); // prints 8
}

Example usage:

fn times_two(number: i32) -> i32 {
    number * 2
}

fn main() {
    let final_number = {
        let y = 10;
        let x = 9; // x starts at 9
        let x = times_two(x); // shadow with new x: 18
        let x = x + y; // shadow with new x: 28
        x // return x: final_number is now the value of x
    };
    println!("The number is now: {}", final_number)
}

Stack Heap Pointers

πŸš€ The Stack is Fast

✈️ Heap is Slower

  • Rust needs to know the size of a variable at compile time.
  • simple variables like i32 go on the stack, because we know their exact size.
    • You always know that an i32 is going to be 4 bytes, because 32 bits = 4 bytes. So i32 can always go on the stack.
  • Some types don't know the size at compile time. But the stack needs to know the exact size. So what do you do?
    • Rust puts the data in the heap, because the heap can have any size of data
    • To find it a pointer goes on the stack.
    • This is fine because we always know the size of a pointer.
    • Computer first goes to the stack, reads the pointer, and follows it to the heap where the data is.

βœ”οΈ Pointer are like table of a content of a book

MY BOOK

TABLE OF CONTENTS

Chapter                        Page
Chapter 1: My life              1
Chapter 2: My cat               15
Chapter 3: My job               23
Chapter 4: My family            30
Chapter 5: Future plans         43
  • The table of content is like the stack that gives you a little bit of info. Then you open a book to the page number to see all the info "like the heap".

Important

  • The pointer you usually see in Rust is called a reference.
  • A reference points to the memory of another value.
  • A reference means you borrow the value, but you don't own it. πŸ¦€πŸ¦€πŸ¦€πŸ¦€
  • It's the same as our book: the table of contents doesn't own the information. It's the chapters that own the information.
  • In Rust, references have a & in front of them. So:
let my_variable = 8  // makes a regular variable
let my_reference = &my_variable // makes a reference.
  • You can also have a reference to a reference, or any number of references.
fn main() {
    let my_number = 15; // This is an i32
    let single_reference = &my_number; //  This is a &i32
    let double_reference = &single_reference; // This is a &&i32
    let five_references = &&&&&my_number; // This is a &&&&&i32
}

& vs *

- "*" Means that you take a reference away back to the value πŸ¦€

fn main(){
  let my_number = 8;
  let my_reference = &my_number;
  println!("{}" , my_reference == my_number) // πŸ›‘ Can't compare a reference to a number
                               ^^ no implementation for `&{integer} == {integer}`
}
fn main(){
  let my_number = 8;
  let my_reference = &my_number;
  println!("{}" , *my_reference == my_number) // βœ”οΈ
}

Strings

  • String

    • little slower.
    • has more functions.
    • it is a pointer with a data in the heap.
    • Owned type.
    • // we can create String like this:
      fn main(){
      let var = String::from("Hello, World");
      let var = "Elie".to_string();
      }
      
  • &str

    • string slice
    • Simple String
    • let var = "hello, world";
    • fast
    • "&" because it is a reference to use str.

πŸš€ format! => Creates a String

fn main() {
    let my_name = "Billybrobby";
    let my_country = "USA";
    let my_home = "Korea";

    let _together = format!(
        "I am {} and I come from {} but I live in {}.",
        my_name, my_country, my_home
    );
}

πŸš€ into! => makes an owned type.

// this will not work because Rust doesn't know which owned type you want. Many types can be made form a &str
fn main() {
    let my_string = "Try to make this a String".into(); // ⚠️
}
fn main() {
    let my_string: String = "Try to make this a String".into(); // βœ”οΈ
}

const

  • Rust will not use type inference.
  • Values that do not change.
  • more popular.
  • Shadowing is not allowed. Shadowing
const my_number = 8; // πŸ›‘ Will not work there is no type!! also will give a warning; should have an uppercase name.
const MY_NUMBER :i8 = 8; // βœ”οΈ

static

  • Rust will not use type inference.
  • Values that do not change.
  • Has a fixed memory location.
  • Can act as a global var.
  • Shadowing is not allowed. Shadowing
static SEASONS: [&str; 4] = ["Spring", "Summer", "Fall", "Winter"];

let

  • used to introduce a new set of variables into the current scope, as given by a pattern.
  • immutable by default.
  • use mut to make it mutable.

References

1️⃣ OwnerShip: Who owns a value.

2️⃣ In Rust there is only one owner for a piece of data.

fn main(){
    let country = String::from("Austria"); // The owner of data.
    let ref_one = &country;           // just looking at it.
    let ref_two = & country;          // just looking at it.
    println!("{} , {} , {}" , country , ref_one , ref_two); // βœ”οΈ
}

This prints Austria.


fn return_str() -> &str{
    let country = String::from("Austria");   // create a String
    let country_ref = &country;             // create a ref for that string
    country_ref                             // return the ref πŸ›‘ the country dies here so the ref dies with it.
}
fn main(){
    let country_name = return_str(); // πŸ›‘ Wrong ...
}
  • function return_str() creates a String.
  • then it creates a reference to that String.
  • Then it tries to return the reference.
  • The String lives inside the function and then it dies.
  • Now the ref is pointing to a data that no longer exist !!
  • πŸ¦€πŸ¦€ You own a string and you can pass it around. But it's references will die once the owner die.
fn return_str() -> String{
    let country = String::from("Austria");
    country
}
fn main(){
    let country_name = return_str(); // βœ”οΈ  here the ownership goes from country to country_name. βœ”οΈ
}
  • here the ownership goes from country to country_name. βœ”οΈ

Mutable References

  • Regular reference gives you a view to the data. &number
    • You can have as many immutable references as you want.
  • mutable reference gives you the ability to change the data. &mut number
    • the owner must be mutable if you want to use mutable reference.
    • You can have ONLY ONE mutable reference.
  • πŸ“ You can't have immutable and mutable reference together.
fn main(){
  let my_number = 7;
  let ref = &mut my_number; // ⚠️ WRONG this will not make my_number mutable .. it has to be mutable when created
}
fn main(){
  let mut my_number = 7;
  let num_ref = &mut my_number; // βœ”οΈ

*num_ref += 10;    // to change the value we use *
println!("{}" , my_number); // 17
}
fn main(){
  let mut number =6;
  let number_ref = &number;
  let number_change = &mut number // πŸ›‘ we cant borrow number as mutable because it is also borrowed as immutable
  *number_change += 10; // πŸ›‘
  println!("{}" , number_ref); // πŸ›‘
}
fn main(){
  let mut number = 6;
  let number_change = &mut number; // create a mut ref
  *number_change += 10; // βœ”οΈ This is fine because in this point we only have one mutable ref
  let number_ref = &number;
  println!("{}" , number_ref); // βœ”οΈ
}

Giving References to Functions

  • we can pass refs to functions both mutable and immutable.
fn print_country(country_name: String) {
  println!("{}", country_name)
}
fn main(){
  let country = String::from("Austria");
  print_country(country);  // Value moved here. the function now own the data. The data will be removed once the fuc dies.
  print_country(country); // πŸ›‘Error value used after move. the data does not exist anymore!
}

We can solve this problem by passing a ref to the function

fn print_country(country_name: &String) {
  println!("{}", country_name)
}
fn main(){
  let country = String::from("Austria");
  print_country(&country);  // βœ”οΈ the func will not take ownership
  print_country(&country); // βœ”οΈ
  println!("{}",country); // βœ”οΈ
}

example for passing mutable ref

fn add_and_print_hungary(country_name: &mut String){
  country_name.push_str("-Hungry");
  println!("Now it says: {}" , country_name);
}
fn main() {
  let mut country = String::from("Austria");
  add_and_print_hungary(&mut country);
}

Traits

  • You can think of Traits as features or the ability.
  • e.g String does not have copy Traits but it has a clone.
  • e.g i32 has a copy trait
  • The types that have the copy trait will be copied when they are sent to a function.

  • String doesn't have a copy.

  • This will fail because country var will be dropped when finished with the first print

fn print_country(country: String){
    println!("{}", country);
}
fn main(){
    let country;
    print_country(country); // var country will be dropped and String does not have a copy Trait
    print_country(country); // πŸ›‘ country now does not exist.
}
  • we can fix this with the clone trait
fn print_country(country: String){
    println!("{}", country);
}
fn main(){
    let country = String::from("Syria");
    print_country(country.clone()); // var country will be cloned
    print_country(country); // βœ”οΈ country now does not exist.
}
  • i32 has a copy trait.
fn print_number(number: i32){
    println!("{}", number);
}
fn main(){
    let my_number = 8;
    print_number(my_number); // βœ”οΈ the value will not be dropped here because a copy of the data will be sent to the function.
    print_number(my_number); // βœ”οΈ because this implement a copy trait the value still exist
}

clone

The Clone Trait makes a copy and pass it to a function.

fn print_country(country: String){
    println!("{}" , country);
}

fn main(){
    let country = String::from("Austria");
    print_country(country.clone());
    print_country(country);
}

Arrays

  • Array items are fixed in size.
  • Array items have the same type.
  • Fast
fn main(){
    let my_array = ["a" ; 40];
    println!("{:?}" , my_array);
}

Array Slicing

  • use a ref
fn main(){
    let my_arr = [1,2,3,4,5,6,7,8,9,10];

    let three_to_five = &my_arr[2..5];
    let start_at_two = &my_arr[1..];
    println!("three to five {:?} , start at two {:?}" , three_to_five , start_at_two);
}

Vecs

  • not as performant as arrays.
  • only one type.
fn main(){
    let name1 = String::from("Elie");
    let name2 = String::from("Maatouk");

    let mut my_vec = Vec::new();
    my_vec.push(name1);
    my_vec.push(name2);

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

fn main(){
    let name1 = String::from("Elie");
    let name2 = String::from("Maatouk");
    let mut my_vec: Vec<String> = Vec::new();
    my_vec.push(name1);
    my_vec.push(name2);

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

}

fn main(){
    let my_vec = vec![1,2,3];
    println!("{:?}" , my_vec);
}

Exercise

  • leet code
  • Given an integer array nums and an integer val, remove all occurrences of val in nums in-place.
  • The relative order of the elements may be changed.
  • You must instead have the result be placed in the first part of the array nums.
  • More formally, if there are k elements after removing the duplicates, then the first k elements of nums should hold the final result.

Example 1:

Input: nums = [3,2,2,3], val = 3
Output: 2, nums = [2,2,_,_]
Explanation: Your function should return k = 2,
with the first two elements of nums being 2.
It does not matter what you leave beyond the returned k (hence they are underscores).

    pub fn remove_element(nums: &mut Vec<i32>, val: i32) -> i32 {
        let mut index = 0;
        for i in 0..nums.len(){
            if nums[i] != val {
                nums[index] = nums[i];
                index += 1;
            }
        }
        println!("So we take the first {} , from the vec {:?}", index , nums);
        index as i32
    }
fn main() {
    remove_element(&mut vec![3,2,2,3],3);
}

Tuples

  • you can have multiple types in tuples.
  • to get items inside a tuple .. use tuple.itemNumber ex. tuple.5
  • you can destructure items.
fn main(){
    let tu = (7,5,"Elie", [12,3], vec![5,3]);
    println!("{:?}",tu);
    println!("item number 3 is {}" , tu.2);
    let (a,b,..) = ("elie" , "maatouk" ,  2 , [1,2,3]);
    println!("{}" , b);
    println!("{}" , a);

}

if else

fn main(){
    let my_number = 7;
    if my_number == 7 {
        println!("The Number is sever")
    } else if my_number >= 7 {
        println!("the number is greater than seven")
    } else {
        println!{"the number is smaller than seven"};
    }
}

match

  • the below code will fail ... because we didn't give it all possibilities. "non-exhaustive pattern"
fn main(){
    let my_number: u8 =5;
    match my_number{
        8 => println!("it is a 8"),
        10 => println!("it is a 10"),
    }  // πŸ›‘ non-exhaustive patterns: means that you didn't think of everything
}
  • to match everything else use "_"
fn main(){
    let my_number: u8 =5;
    match my_number{
        8 => println!("it is a 8"),
        10 => println!("it is a 10"),
        _ => println!("it's something else")
    }
}

match using tuples

  • you can match with a tuple ..
fn main(){
    let sky = "cloudy";
    let temp = "warm";
    match (sky , temp){
        ("cloudy","cold") => println!("it is cloudy and warm"),
        ("sunny","warm") => println!("it is sunny and warm"),
        ("cloudy", "warm") => println!("it is cloudy and warm"),
        _ => println!("it is something else")
    }
}

match with if

  • you can use if inside the match statement
fn main(){
    let children = 5;
    let married = true;
    match (children , married){
        (children , married) if married && children == 4 => println!("married with 4 children"),
        (children , married) if married && children == 5 => println!("married with 5 children"),
        (children , married) if married && children > 0 => println!("married with {} children" , {children}),
        _ => println!("{} , number of children {}" , married , children)
    }
}

match example with 3 vars

fn match_colors(rgb: (u8,u8,u8)){
    match rgb {
        (r,_,_) if r < 10 => println!("not much red"),
        (_,g,_) if g < 10 => println!("not much green"),
        (_,_,b) if b < 10 => println!("not much blue"),
            _ => println!("looks good"),
    }
}
fn main(){
    let a = (200,0,0);
    let b = (50,50,50);
    let c = (200,50,0);
    match_colors(a);
    match_colors(b);
    match_colors(c);
}

match must return same type

fn main(){
    let my_number = 10;
    let v = match my_number {
        10 => 8,
        _ => "Something else" // πŸ›‘ πŸ›‘ this will not work return type must be the same!!
    }
}
  • the same rule goes for any checking
fn main(){
    let v = 2;
    let a = if v == 2 {5} else ("something else"); // πŸ›‘ πŸ›‘ wrong return type must be the same !!
}

using var in the return

  • to use the match condition in the return portion by giving it a name.
fn match_number(input: i32) {
    match input {
    // here we gave the match condition name "number" now we can use number in the return portion
    number @ 4 => println!("{} is an unlucky number in China ", number),
    number @  13 => println!("{} is unlucky in North America, lucky in Italy! In bocca al lupo!", number),
    _ => println!("Looks like a normal number"),
    }
}

fn main() {
    match_number(50);
    match_number(13);
    match_number(4);
}

Intro

  • creating your own type, your own Data Structure.
  • By convention use upper camel case UpperCamelCase

Struct Types

  • Unit Struct

  • Tuple Struct

  • Named Struct

    struct FileDirectory;   // βœ”οΈ Unit Struct
    struct Color(u8,u8,u8); // βœ”οΈ Tuple Struct
    struct SizeAndColor {   // βœ”οΈ Named Struct
        size: u32,
        color:  Color
    }
    fn main(){
        let _my_directory = FileDirectory;
        let my_color = Color(50,60,0);
        let my_color2 = Color(50,60,0);
    
        let size_color = SizeAndColor {
            size: 150,
            color:  my_color2
        };
        println!("The First Color is: {} " ,my_color.0);
    }
    

Printing a Struct

  • You can't use the display formatter println("{}") nor the debug formatter println{:?} to print Struct.
  • from the compiler ==> note: add #[derive(Debug)] to SizeAndName or manually impl Debug for SizeAndName
struct SizeAndName {   // πŸ›‘ the trait `Debug` is not implemented for `SizeAndName` 
    size: u8,
    name: String 
}

fn main(){
    let s_c = SizeAndName{
        size: 150,
        name: String::from("elie")
    };
    println!("{:?}" , s_c);
}

 #[derive(Debug)] 
struct SizeAndName {
    size: u8,
    name: String 
}

fn main(){
    let s_c = SizeAndName{
        size: 150,
        name: String::from("elie")
    };
    println!("{:?}" , s_c);
}

πŸƒ Exercise 1

  • Create a struct called Person that has the following fields:

    • name: a string representing the person's name

    • age: an integer representing the person's age

    • email: an optional string representing the person's email address (this field should default to None)

  • Then, write a function called create_person that takes in a name and age as arguments and returns a Person struct with those values.

  • After that, write a main function that creates a Person struct using create_person and prints out the values of its fields.

#[derive(Debug)]
struct Person {
    name: String,
    age: u8,
    email: Option<String>
}

fn create_person (name: String , age: u8) -> Person{
    Person {
        name,
        age,
        email: None
    }
}

fn main(){
    let me = create_person("Elie".to_string() , 33);
    println!("{:#?}" , me);
}

πŸƒ Exercise 2

  • Create a struct called Rectangle that has the following fields:

    • width: a float representing the width of the rectangle

    • height: a float representing the height of the rectangle

  • Then, write methods for the Rectangle struct that calculate its:

    • area
    • perimeter
    • diagonal length (use the Pythagorean theorem to calculate this)
  • Finally, write a main function that creates a Rectangle struct with a width of 4.0 and a height of 3.0, and prints out its area, perimeter, and diagonal length.

#[derive(Debug)]
struct Rectangle {
    width: f32,
    height: f32
}

impl Rectangle {
    fn calculate_area(&self) -> f32{
        self.width * self.height
    }
    fn calculate_perimeter(&self) -> f32 {
        2.0 * (self.width + self.height)
    }
    fn calculate_diagonal_length(&self) -> f32 {
        (self.width.powi(2) + self.height.powi(2)).sqrt()
    }
}

fn main() {
    let rec1 = Rectangle { width: 3.0, height: 4.5 };
    let area = rec1.calculate_area();
    let perimeter = rec1.calculate_perimeter();
    let diagonal_length = rec1.calculate_diagonal_length();
    println!("Area: {}", area);
    println!("Perimeter: {}", perimeter);
    println!("Diagonal length: {}", diagonal_length);
}

Intro

  • πŸš€ enum is about choices.
  • πŸš€ They can also hold data.
  • πŸš€ Each variant has like an invisible number that you can reference ..
    • enum = enumeration => a list of choices.
    • Upper camel case.
  • enum vs struct.
    • struct is a and b and c and d ...
    • enum is a or b or c ...

#![allow(unused)]
fn main() {
enum ChoiceOfThings {
    Up,
    Down,
    Left,
    Right
}
}

Example

enum ThingsInTheSky {
    Sun,
    Stars
}

fn create_skyState (time: i32) -> ThingsInTheSky{
    match time {
        6..=18 => ThingsInTheSky::Sun,                   // 6..=18 means including 18
        _ => ThingsInTheSky::Stars
    }
}


fn check_sky_state(state: &ThingsInTheSky) {
    match state {
        ThingsInTheSky::Sun => println!("I can see the Sun"),
        ThingsInTheSky::Stars => println!("I can see the Stars"),
    }
}


fn main(){
    let time = 8;
    let sky_state = create_skyState(time);
    check_sky_state(&sky_state);
}

enum data

enum ThingsInTheSky {
    Sun(String),
    Stars(String)
}

fn create_skyState (time: i32) -> ThingsInTheSky{
    match time {
        // πŸ„ now when we match we can add data πŸ„
        6..=18 => ThingsInTheSky::Sun(String::from("I can see the Sun")),
        _ => ThingsInTheSky::Stars(String::from("I can see the Stars")),
    }
}


fn check_sky_state(state: &ThingsInTheSky) {
    match state {
        // πŸ„ Now we can access the data
        ThingsInTheSky::Sun(desc) => println!("{}",desc),
        ThingsInTheSky::Stars(desc) => println!("{}",desc),
    }
}


fn main(){
    let time = 8;
    let sky_state = create_skyState(time);
    check_sky_state(&sky_state);
}

use Statement

  • if we don't want to write the enum name every time we can make use of the use statement.
  • it is like an import.

Before ...

enum Mood{
    Happy,
    Sleepy,
    NotBad,
    Angry
}

fn match_mood(mood: &Mood) -> i32{

    let happiness_lvl = match mood {
        Mood::Happy => 10,
        Mood::Sleepy => 6,
        Mood::NotBad => 7,
        Mood::Angry => 2,
    };
    happiness_lvl

}

fn main(){
    let mood = Mood::NotBad;
    let happiness_lel = match_mood(&mood);
    println!("Happiness level is {}" , happiness_lel )
}

After:

enum Mood{
    Happy,
    Sleepy,
    NotBad,
    Angry
}

fn match_mood(mood: &Mood) -> i32{
    // πŸ„ import every thing from this enum..
    use Mood::*;
    let happiness_lvl = match mood {
        Happy => 10,
        Sleepy => 6,
        NotBad => 7,
        Angry => 2,
    };
    happiness_lvl

}

fn main(){
    let mood = Mood::NotBad;
    let happiness_lel = match_mood(&mood);
    println!("Happiness level is {}" , happiness_lel )
}

enum index

  • if an enum does not have data then it will be assigned an index that can be accessed.
enum Test {
    Test1,
    Test2
}
fn main(){
    use Test::*;
    let tests = vec![Test1,Test2];
    for i in tests{
        println!("{}", i as i32);
    }
}
  • usually they start at 0 and increment by one ... but we can give them custom value.
enum Star {
    BrownDwarf = 10 ,    // usually it is 0
    RedDwarf =50,      // usually it is 1
    YellowStar = 100,    // usually it is 2
    RedGiant = 1000,       // usually it is 3
    DeadStar ,
}

fn main(){
    use Star::*;
    let v = vec![BrownDwarf,RedDwarf, YellowStar,RedGiant,DeadStar];
    for i in v {
        println!("{}" , i as i32);
    }
}
enum Star {
    BrownDwarf = 10 ,    // usually it is 0
    RedDwarf =50,      // usually it is 1
    YellowStar = 100,    // usually it is 2
    RedGiant = 1000,       // usually it is 3
    DeadStar ,                  // πŸ„πŸ„ if left unassigned it will increment by one from the last one...
}

fn main(){
    use Star::*;
    let v = vec![BrownDwarf,RedDwarf, YellowStar,RedGiant];
    for i in v {
        match i as u32 {
            size if size <= 80 => println!("Not the biggest Star"),
            size if size > 80 => println!("This is a good sized star"),
            _ => println!("other star")
        }
    }
    println!("the Dead start is {}" , DeadStar as i32);
}

exercise 1

  • Create a Shape enum with the following variants:

    • Circle(f32): represents a circle with a given radius
    • Rectangle(f32, f32): represents a rectangle with given width and height
    • Triangle(f32, f32, f32): represents a triangle with given side lengths
    • Implement a perimeter method for the Shape enum that calculates and returns the perimeter of the shape. The perimeter of a circle is its circumference, and the perimeter of a - rectangle is twice the sum of its width and height. The perimeter of a triangle is the sum of its three sides.

Once you've implemented the perimeter method, create instances of each variant of the Shape enum and call the perimeter method on them.

use std::f32::consts::PI;

enum Shape {
    Circle(f32),
    Rectangle(f32,f32),
    Triangle(f32,f32,f32)
}


impl Shape {
    fn perimeter(&self){
        match self {
            Shape::Circle(x) =>  println!("{}" ,2.0 * x * PI) ,
            Shape::Rectangle(x,y ) => println!( "{}" ,2.0 * (x + y)) ,
            Shape::Triangle(x,y ,z ) => println!("{}" ,x + y + z)
        }
    }
}

fn main(){

    let circle = Shape::Circle(5.0);
    let rectangle = Shape::Rectangle(3.0 , 4.0);
    let triangle = Shape::Triangle(5.0 , 6.0 , 7.0);

    circle.perimeter();
    rectangle.perimeter();
    triangle.perimeter();

}

πŸƒ exercise 2

  • you are building a system to manage a library of books. Each book has a title, an author, and a category.

  • The categories are: "Fiction", "Non-fiction", "Science fiction", "Mystery", and "Romance".

  • Create an enum called Category to represent these categories, and a struct called Book to represent a book.

  • The Book struct should have fields for the title, author, and category.

  • Write a function called is_romance that takes a Book as input and returns true if the book's category is "Romance", and false otherwise.

  • Finally, create a main function that creates three instances of Book with different titles, authors, and categories (at least one of which should be "Romance"), and calls the is_romance function on each one to check if it is a romance book. Use println! to print the result for each book.

enum Category {
    Fiction,
    NonFiction,
    ScienceFiction,
    Mystery,
    Romance,
}

struct Book {
    title: String,
    author: String,
    category: Category,
}

fn is_romance(book: Book) -> bool {
    match book.category {
        Category::Romance => true,
        _ => false,
    }
}

fn main() {
    let book1 = Book {
        title: "Pride and Prejudice".to_string(),
        author: "Jane Austen".to_string(),
        category: Category::Romance,
    };
    let book2 = Book {
        title: "The Hitchhiker's Guide to the Galaxy".to_string(),
        author: "Douglas Adams".to_string(),
        category: Category::ScienceFiction,
    };
    let book3 = Book {
        title: "The Da Vinci Code".to_string(),
        author: "Dan Brown".to_string(),
        category: Category::Mystery,
    };

    println!("Book 1 is a romance: {}", is_romance(book1));
    println!("Book 2 is a romance: {}", is_romance(book2));
    println!("Book 3 is a romance: {}", is_romance(book3));
}

πŸƒ exercise 3

  • Write a program that defines an enum Direction with four variants: Up, Down, Left, and Right.
  • Define a struct Point with x and y fields of type i32. Add a method to the Point struct called move_point that takes a Direction argument and moves the point in that direction by one unit (i.e., increments or decrements the x or y field by one, depending on the direction).
  • Finally, write a main function that creates a Point at (0, 0), moves it up and to the right, and prints the final coordinates of the point.
enum Direction {
    Up,
    Down,
    Left,
    Right
}

struct Point {
    x: i32,
    y: i32
}

impl Point {
    fn move_point(&mut self , direction: Direction){
        match direction {
            Direction::Up => self.y += 1,
            Direction::Down=> self.y -= 1,
            Direction::Right => self.x +=1 ,
            Direction::Left => self.x -=1
        }
    }
}

fn main(){
    let mut player = Point{x: 0, y: 0};
    player.move_point(Direction::Up);
    player.move_point(Direction::Left);
    println!("The player is at point x: {} , y: {}", player.x  , player.y)

}