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
mutto 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 formatterprintln{:?}to print Struct. - from the compiler ==> note: add
#[derive(Debug)]toSizeAndNameor manuallyimpl 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) }