├── .DS_Store ├── .gitignore ├── README.md ├── apps └── minigrep │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ ├── file.txt │ ├── main.rs │ ├── mod.rs │ └── utils.rs ├── concepts ├── .DS_Store ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ ├── .DS_Store │ ├── concepts │ ├── box_smart_pointer.rs │ ├── cargo_and_external_deps.rs │ ├── datatypes.rs │ ├── enums.rs │ ├── error_handling.rs │ ├── functions.rs │ ├── generics.rs │ ├── loops.rs │ ├── matching.rs │ ├── memory_management │ │ ├── basics.rs │ │ ├── borrowing_and_references.rs │ │ ├── lifetimes.rs │ │ ├── mod.rs │ │ └── ownership.rs │ ├── mod.rs │ ├── option.rs │ ├── scopes.rs │ ├── structs.rs │ └── traits.rs │ └── main.rs └── data-structures-and-algorithms ├── Cargo.lock ├── Cargo.toml └── src ├── main.rs ├── search ├── binary.rs ├── linear.rs └── mod.rs └── sorting ├── bubble_sort.rs ├── mod.rs └── quick_sort.rs /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratik-codes/learn-rust/5d9125c34485b80c2f4a9aa689ff49b6b07f3d3a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Rust 2 | 3 | Welcome to the **learn-rust** repository! This is your go-to resource for learning the Rust programming language. Whether you're a beginner or an experienced developer, this repository will guide you through the essentials of Rust and help you master its features. 4 | 5 | ## Table of Contents 6 | 7 | ```plaintext 8 | Modules 9 | │ 10 | └── Apps 11 | ├── Minigrep 12 | ├── Concepts 13 | ├── Data Types 14 | ├── Loops 15 | ├── Scopes 16 | ├── Functions 17 | ├── Structs 18 | ├── Enums 19 | ├── Traits 20 | ├── Error Handling 21 | └── Memory Management 22 | ├── basics 23 | ├── ownership 24 | ├── borrowing 25 | ├── lifetimes 26 | ``` 27 | 28 | ## Introduction 29 | 30 | Rust is a systems programming language that is fast, memory-efficient, and guarantees thread safety. It’s designed to help developers create reliable and efficient software, without sacrificing speed or safety. 31 | 32 | This repository is structured to help you learn Rust step by step. Each concept is broken down into modules with clear examples and explanations. 33 | 34 | ## Getting Started 35 | 36 | To start using the code in this repository, clone the repo and navigate to the project directory: 37 | 38 | ```bash 39 | git clone https://github.com/yourusername/learn-rust.git 40 | cd learn-rust 41 | ``` 42 | 43 | Ensure you have Rust installed. if you have run this project by: 44 | ```bash 45 | cargo run 46 | ``` 47 | 48 | ## Contributing 49 | Contributions are welcome! If you have improvements, suggestions, or new examples, please feel free to submit a pull request. For major changes, please open an issue to discuss what you would like to contribute. 50 | 51 | 52 | ## References 53 | https://doc.rust-lang.org/book/title-page.html 54 | -------------------------------------------------------------------------------- /apps/minigrep/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /apps/minigrep/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "minigrep" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /apps/minigrep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minigrep" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /apps/minigrep/src/file.txt: -------------------------------------------------------------------------------- 1 | This is a demo text 2 | 3 | Now we’ll add functionality to read the file specified in the file_path argument. First, we need a sample file to test it with: we’ll use a file with a small amount of text over multiple lines with some repeated words. Listing 12-3 has an Emily Dickinson poem that will work well! Create a file called poem.txt at the root level of your project, and enter the poem “I’m Nobody! Who are you?” 4 | -------------------------------------------------------------------------------- /apps/minigrep/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process; 2 | 3 | mod utils; 4 | 5 | fn main() { 6 | println!("Mini grep by Pratik..."); 7 | 8 | let args: Vec = utils::get_command_line_args(); 9 | let config = utils::Config::build(&args).unwrap_or_else(|err| { 10 | println!("Problem parsing arguments: {err}"); 11 | process::exit(1); 12 | }); 13 | let file_content = match utils::read_file_content(&config.file_path) { 14 | Ok(content) => content, // If successful, assign the file content 15 | Err(err) => match err { 16 | err => { 17 | println!("Error reading the file: {}", err); 18 | std::process::exit(1); 19 | } 20 | }, 21 | }; 22 | let matched_searches = utils::search_string(&config.query, &file_content); 23 | 24 | println!("Searching for {}", config.query); 25 | println!("#############################################"); 26 | println!("In file {}", config.file_path); 27 | println!("#############################################"); 28 | println!("File content: {:?}", file_content); 29 | println!("#############################################"); 30 | println!("matched searches {:?}", matched_searches); 31 | } 32 | -------------------------------------------------------------------------------- /apps/minigrep/src/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | -------------------------------------------------------------------------------- /apps/minigrep/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io::Error; 4 | 5 | pub struct Config { 6 | pub query: String, 7 | pub file_path: String, 8 | } 9 | 10 | // parse the args to get config 11 | // using clone we loose a little on speed and performance but we gain on code redablitiy by not 12 | // introducing lifetimes 13 | impl Config { 14 | pub fn build(args: &[String]) -> Result { 15 | if args.len() < 3 { 16 | return Err("not enought arguments"); 17 | } 18 | let query = args[1].clone(); 19 | let file_path = args[2].clone(); 20 | 21 | Ok(Config { query, file_path }) 22 | } 23 | } 24 | 25 | pub fn get_command_line_args() -> Vec { 26 | // Collects the command-line arguments into a vector 27 | env::args().collect() 28 | } 29 | 30 | pub fn read_file_content(file_path: &String) -> Result { 31 | let content = fs::read_to_string(file_path); 32 | match content { 33 | Ok(data) => Ok(data), 34 | Err(err) => Err(err), // Propagate the error 35 | } 36 | } 37 | 38 | pub fn search_string<'a>(query: &str, content: &'a str) -> Result, &'static str> { 39 | if content == "" { 40 | return Err("empty content"); 41 | } 42 | 43 | let mut results = Vec::new(); 44 | 45 | for line in content.lines() { 46 | if line.contains(query) { 47 | results.push(line); 48 | } 49 | } 50 | 51 | Ok(results) 52 | } 53 | -------------------------------------------------------------------------------- /concepts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratik-codes/learn-rust/5d9125c34485b80c2f4a9aa689ff49b6b07f3d3a/concepts/.DS_Store -------------------------------------------------------------------------------- /concepts/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /concepts/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "learn-rust" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /concepts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "learn-rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /concepts/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratik-codes/learn-rust/5d9125c34485b80c2f4a9aa689ff49b6b07f3d3a/concepts/src/.DS_Store -------------------------------------------------------------------------------- /concepts/src/concepts/box_smart_pointer.rs: -------------------------------------------------------------------------------- 1 | // box in rust are the most straight forward smart pointer. whos types are written Box 2 | // Boxes allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data 3 | // When should we use Box? 4 | // When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size 5 | // When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so 6 | // When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type 7 | 8 | // Why do we need Box? 9 | // Transferring ownership of a large amount of data can take a long time because the data is copied around on the stack. To improve performance in this situation, we can store the large amount of data on the heap in a box. Then, only the small amount of pointer data is copied around on the stack, while the data it references stays in one place on the heap. 10 | 11 | pub fn run() { 12 | println!("\n#################################"); 13 | println!("Box Smart Pointer"); 14 | println!("#################################"); 15 | 16 | syntax(); 17 | } 18 | 19 | fn syntax() { 20 | let b = Box::new(5); 21 | println!("b = {}", b); 22 | } 23 | 24 | trait Animal { 25 | fn make_sound(&self); 26 | } 27 | 28 | struct Dog; 29 | 30 | impl Animal for Dog { 31 | fn make_sound(&self) { 32 | println!("Woof!"); 33 | } 34 | } 35 | 36 | // let my_pet: Box = Box::new(Dog); 37 | // my_pet.make_sound(); // Outputs "Woof!" 38 | -------------------------------------------------------------------------------- /concepts/src/concepts/cargo_and_external_deps.rs: -------------------------------------------------------------------------------- 1 | // Just like the nodejs ecosystem has npm, the rust ecosystem has cargo 2 | // Cargo is a package manager for rust, which means we can use it to bring packages (crates in case of rust) to our projects 3 | 4 | // To create a new project, we can use the following command 5 | // cargo new 6 | // This will create a new project with the following structure 7 | // 8 | // ├── Cargo.toml 9 | // └── src 10 | // └── main.rs 11 | 12 | // Cargo.toml is the configuration file for the project 13 | // It contains the metadata about the project and the dependencies 14 | 15 | // To add a dependency to the project, we can add the following line to the Cargo.toml file 16 | // [dependencies] 17 | // = "" 18 | 19 | // To install the dependencies, we can run the following command 20 | // cargo build 21 | // This will download the dependencies and build the project 22 | 23 | // To run the project, we can use the following command 24 | // cargo run 25 | 26 | // To update the dependencies, we can use the following command 27 | // cargo update 28 | 29 | // To remove the dependencies, we can remove the line from the Cargo.toml file and run the following command 30 | // cargo build 31 | 32 | use rand::{Rng, thread_rng}; 33 | fn create_random_num() { 34 | let mut rng = thread_rng(); 35 | let n: u32 = rng.gen(); 36 | println!("Random number: {}", n); 37 | } 38 | 39 | use chrono::{Local, Utc}; 40 | fn main() { 41 | // Get the current date and time in UTC 42 | let now = Utc::now(); 43 | println!("Current date and time in UTC: {}", now); 44 | 45 | // Format the date and time 46 | let formatted = now.format("%Y-%m-%d %H:%M:%S"); 47 | println!("Formatted date and time: {}", formatted); 48 | 49 | // Get local time 50 | let local = Local::now(); 51 | println!("Current date and time in local: {}", local); 52 | } 53 | 54 | 55 | // What all libraries does rust have? 56 | // A lot of them 57 | // https://actix.rs/ - Extremely fast http server 58 | // https://serde.rs/ - Serializing and deserialising data in rust 59 | // https://tokio.rs/ - Asynchronous runtime in rust 60 | // https://docs.rs/reqwest/latest/reqwest/ - S -------------------------------------------------------------------------------- /concepts/src/concepts/datatypes.rs: -------------------------------------------------------------------------------- 1 | // u is for unsigned integers (positive numbers) 2 | // i is for signed integers (positive and negative numbers) 3 | // The number after u or i indicates the number of bits 4 | // For example, u8 is an unsigned integer with 8 bits 5 | // The number of bits determines the range of values that can be stored 6 | // For example, an u8 can store values from 0 to 255 7 | // i8 can store values from -128 to 127 8 | // The number of bits also determines the memory used by the variable 9 | // For example, u8 uses 1 byte of memory (8 bits) 10 | // i8 uses 1 byte of memory (8 bits) 11 | // i32 uses 4 bytes of memory (32 bits) 12 | // i64 uses 8 bytes of memory (64 bits) 13 | // i128 uses 16 bytes of memory (128 bits) 14 | // The default integer type is i32 15 | 16 | pub fn run() { 17 | // datatype concepts 18 | println!("\n#################################"); 19 | println!("Data Types Module"); 20 | println!("#################################\n"); 21 | integer_types(); 22 | floating_point_types(); 23 | boolean_type(); 24 | array_type(); 25 | tuple_type(); 26 | character_type(); 27 | vectors(); 28 | immutable_variable_example(); 29 | mutable_variable_example(); 30 | } 31 | 32 | fn integer_types() { 33 | let x: i32 = 10; 34 | let y: u8 = 255; 35 | println!("Integer Types - x(i32): {}, y(u8): {}", x, y); 36 | } 37 | 38 | fn floating_point_types() { 39 | let x: f32 = 2.5; 40 | let y: f64 = 3.14; 41 | println!("Floating Point Types - x: {}, y: {}", x, y); 42 | } 43 | 44 | fn boolean_type() { 45 | let is_rust_awesome: bool = true; 46 | println!("Boolean Type - Is Rust awesome? {}", is_rust_awesome); 47 | } 48 | 49 | fn character_type() { 50 | let letter: char = 'R'; 51 | let emoji: char = '😊'; 52 | println!("Character Type - letter: {}, emoji: {}", letter, emoji); 53 | } 54 | 55 | fn tuple_type() { 56 | let tuple: (i32, f64, char) = (42, 3.14, 'R'); 57 | let (x, y, z) = tuple; 58 | println!("Tuple Type - x: {}, y: {}, z: {}", x, y, z); 59 | } 60 | 61 | fn array_type() { 62 | let array: [i32; 3] = [1, 2, 3]; 63 | println!("Array Type - Array: {:?}", array); 64 | } 65 | 66 | fn immutable_variable_example() { 67 | let x = 5; 68 | // x = 6; // This would cause a compile-time error because `x` is immutable 69 | println!("Immutable x: {}", x); 70 | } 71 | 72 | // what is vector? 73 | // A vector is a dynamic array that can grow or shrink in size at runtime 74 | fn vectors() { 75 | let mut v = vec![1, 2, 3]; 76 | v.push(4); 77 | println!("Vector: {:?}", v); 78 | } 79 | 80 | fn mutable_variable_example() { 81 | let mut x = 5; 82 | println!("Before mutation: {}", x); 83 | 84 | x = 6; // This is allowed because `x` is mutable 85 | println!("After mutation: {}", x); 86 | } 87 | -------------------------------------------------------------------------------- /concepts/src/concepts/enums.rs: -------------------------------------------------------------------------------- 1 | // enums 2 | // what are enums? 3 | // - enums are a way to define a type by enumerating its possible variants. 4 | 5 | // Enums in Rust are similar to enums in other languages, but they have some extra features. 6 | // Enums can be used to create custom data types that can be one of a few different variants. 7 | // Enums are useful when you have a fixed set of values that a variable can have. 8 | 9 | pub fn run() { 10 | println!("\n#################################"); 11 | println!("Enums"); 12 | println!("#################################\n"); 13 | 14 | enum_example(); 15 | enums_with_values(); 16 | } 17 | 18 | enum Movement { 19 | // variants 20 | Up, 21 | Down, 22 | Left, 23 | Right, 24 | } 25 | 26 | fn enum_example() { 27 | let player1 = Movement::Up; 28 | let player2 = Movement::Down; 29 | let player3 = Movement::Left; 30 | let player4 = Movement::Right; 31 | 32 | move_player(player1); 33 | move_player(player2); 34 | move_player(player3); 35 | move_player(player4); 36 | } 37 | 38 | fn move_player(_m: Movement) { 39 | // do something 40 | } 41 | 42 | // ******************* Enums with values ************************* 43 | 44 | enum Shape { 45 | Rectangle(u32, u32), 46 | Circle(f64), 47 | Triangle(f64, f64, f64), 48 | } 49 | 50 | fn enums_with_values() { 51 | let rect = Shape::Rectangle(20, 30); 52 | let circle = Shape::Circle(3.14); 53 | let triangle = Shape::Triangle(3.0, 4.0, 5.0); 54 | 55 | calculate_area(rect); 56 | calculate_area(circle); 57 | calculate_area(triangle); 58 | } 59 | 60 | fn calculate_area(shape: Shape) { 61 | match shape { 62 | Shape::Rectangle(width, height) => { 63 | println!("Area of rectangle: {}", width * height); 64 | } 65 | Shape::Circle(radius) => { 66 | println!("Area of circle: {}", 3.14 * radius * radius); 67 | } 68 | Shape::Triangle(a, b, c) => { 69 | let s = (a + b + c) / 2.0; 70 | let area = (s * (s - a) * (s - b) * (s - c)).sqrt(); 71 | println!("Area of triangle: {}", area); 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /concepts/src/concepts/error_handling.rs: -------------------------------------------------------------------------------- 1 | // Error handling 2 | // Rust has a powerful error handling mechanism. It uses the Result enum to return either a value or an error. The Result enum has two variants: Ok and Err. Ok holds the value returned by the function, and Err holds the error message. 3 | // this is similar to try catch in other languages 4 | 5 | 6 | pub fn run() { 7 | println!("\n#################################"); 8 | println!("Error Handling Module"); 9 | println!("#################################\n"); 10 | 11 | error_handling_example(); 12 | } 13 | 14 | fn error_handling_example() { 15 | let result = divide(10, 0); 16 | match result { 17 | Ok(value) => println!("Result is {}", value), 18 | Err(error) => println!("Error is {}", error), 19 | } 20 | 21 | println!("End of error handling example function"); // this will run because the error is handled gracefully 22 | } 23 | 24 | fn divide(x: i32, y: i32) -> Result { 25 | if y == 0 { 26 | return Err("Cannot divide by zero".to_string()); 27 | } 28 | Ok(x / y) 29 | } -------------------------------------------------------------------------------- /concepts/src/concepts/functions.rs: -------------------------------------------------------------------------------- 1 | pub fn run() { 2 | // function concepts 3 | println!("\n#################################"); 4 | println!("Function Module"); 5 | println!("#################################\n"); 6 | 7 | 8 | function_example(); 9 | function_parameters(); 10 | closure_example(); 11 | } 12 | 13 | fn function_example() { 14 | println!("Function Example - Hello, World!"); 15 | } 16 | 17 | fn function_parameters() { 18 | let x = 5; 19 | let y = 10; 20 | let sum = add(x, y); 21 | println!("Function Parameters - Sum: {}", sum); 22 | } 23 | 24 | fn add(x: i32, y: i32) -> i32 { 25 | x + y 26 | } 27 | 28 | // The line let sum = |x, y| x + y; in the given code defines a closure function and assigns it to the variable sum. 29 | // A closure is a special type of function that can capture variables from its surrounding environment. In this case, the closure takes two parameters x and y, and returns the sum of x and y. The closure is defined using the |x, y| syntax, where x and y are the input parameters. 30 | // The closure function can be called just like a regular function. In this code, the closure is called with the parameters x and y using the expression sum(x, y). The result of the closure function, which is the sum of x and y, is then printed to the console using the println! macro. 31 | // In summary, this line defines a closure function that calculates the sum of two numbers, assigns it to the variable sum, and then calls the closure function with specific values for x and y. 32 | fn closure_example() -> fn(i32, i32) -> i32 { 33 | let x = 5; 34 | let y = 10; 35 | let sum = |x, y| x + y; 36 | println!("Function Closure - Sum: {}", sum(x, y)); 37 | sum 38 | } 39 | 40 | -------------------------------------------------------------------------------- /concepts/src/concepts/generics.rs: -------------------------------------------------------------------------------- 1 | // Generics 2 | // - Generics are a way to make code more flexible and reusable. 3 | // - They allow you to write code that can work with any type. 4 | // - Generics are a way to abstract over types. 5 | // We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types. 6 | // things like options and results are use genrics as well. The alphabet T is used to represent the type in them or it can be represented with other alphabet as well. 7 | 8 | use std::cmp::PartialOrd; 9 | 10 | pub fn run() { 11 | println!("\n#################################"); 12 | println!("Generics Module"); 13 | println!("#################################\n"); 14 | 15 | generics_example(); 16 | generic_in_structs(); 17 | } 18 | 19 | fn generics_example() { 20 | let number_list = vec![34, 50, 25, 100, 65]; 21 | 22 | let result = largest(&number_list); 23 | println!("The largest number is {result}"); 24 | } 25 | 26 | // The function signature for largest now reads as follows: 27 | // T is a generic type parameter. hence any type can be passed to the function. 28 | // but it also has a constraint that the type must implement the PartialOrd trait. 29 | // This means we can compare values of type T in the function. 30 | // hence you cant pass any type to the function, it must be a type that implements the PartialOrd trait. which means it should be a type that can be ordered or compared. 31 | // This makes the function very flexible and reusable and safe. 32 | // you can refer to traits.rs for more information on traits. 33 | fn largest(list: &[T]) -> &T { 34 | let mut largest = &list[0]; 35 | 36 | for item in list { 37 | if item > largest { 38 | largest = item; 39 | } 40 | } 41 | 42 | largest 43 | } 44 | 45 | struct Point { 46 | x: T, 47 | y: U, 48 | } 49 | 50 | fn generic_in_structs() { 51 | let _both_integer = Point { x: 5, y: 10 }; 52 | let _both_float = Point { x: 1.0, y: 4.0 }; 53 | let _integer_and_float = Point { x: 5, y: 4.0 }; 54 | } -------------------------------------------------------------------------------- /concepts/src/concepts/loops.rs: -------------------------------------------------------------------------------- 1 | use std::string::String; 2 | 3 | pub fn run() { 4 | // loop concepts 5 | println!("\n#################################"); 6 | println!("Loop Module"); 7 | println!("#################################\n"); 8 | 9 | loop_example(); 10 | while_example(); 11 | for_example(); 12 | } 13 | 14 | fn loop_example() { 15 | let mut x = 0; 16 | loop { 17 | x += 1; 18 | println!("Loop Example - x: {}", x); 19 | if x == 5 { 20 | break; 21 | } 22 | } 23 | } 24 | 25 | fn while_example() { 26 | let mut x = 0; 27 | while x < 5 { 28 | x += 1; 29 | println!("While Example - x: {}", x); 30 | } 31 | } 32 | 33 | // how is char and string stored in memory by rust 34 | // char is stored in 4 bytes in memory 35 | // string is stored in 24 bytes in memory 36 | // these can be seen by using the size_of::() function in the std::mem module 37 | fn for_example() { 38 | let arr = [10, 20, 30, 40, 50]; 39 | for element in arr.iter() { 40 | println!("For Example - element: {}", element); 41 | } 42 | 43 | let name: String = String::from("Rust"); 44 | 45 | for character in name.chars() { 46 | println!("For Example - on character: {}", character); 47 | } 48 | 49 | for byte in name.bytes() { 50 | println!("you can also iterate over bytes of a string{}", byte); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /concepts/src/concepts/matching.rs: -------------------------------------------------------------------------------- 1 | // pattern matching 2 | // Matching in rust is similar to switch in other languages, but it is more powerful. It can be used to destructure structs, enums, tuples, and more. 3 | 4 | 5 | pub fn run() { 6 | println!("\n#################################"); 7 | println!("Matching Module"); 8 | println!("#################################\n"); 9 | 10 | match_example(); 11 | match_enums(Shape::Circle); 12 | match_tuples(); 13 | } 14 | 15 | fn match_example() { 16 | let x = 5; 17 | match x { 18 | 1 => println!("x is 1"), 19 | 2 => println!("x is 2"), 20 | 3 => println!("x is 3"), 21 | 4 => println!("x is 4"), 22 | 5 => println!("x is 5"), 23 | _ => println!("x is not 1, 2, 3, 4, or 5"), 24 | } 25 | } 26 | 27 | // matching example with the help of enums 28 | enum Shape { 29 | Circle, 30 | // Square, 31 | // Rectangle, 32 | // Triangle, 33 | } 34 | 35 | fn match_enums(shape: Shape) { 36 | match shape { 37 | Shape::Circle => println!("Shape is a circle"), 38 | // Shape::Square => println!("Shape is a square"), 39 | // Shape::Rectangle => println!("Shape is a rectangle"), 40 | // Shape::Triangle => println!("Shape is a triangle"), 41 | } 42 | } 43 | 44 | // matching with tuples 45 | fn match_tuples() { 46 | let tuple = (0, 1); 47 | // what happens here is that the first value of the tuple is matched with the first value of the tuple in the match arm 48 | // and the second value of the tuple is matched with the second value of the tuple in the match arm 49 | match tuple { 50 | (0, y) => println!("y is {}", y), 51 | (x, 0) => println!("x is {}", x), 52 | _ => println!("No match"), 53 | } 54 | } -------------------------------------------------------------------------------- /concepts/src/concepts/memory_management/basics.rs: -------------------------------------------------------------------------------- 1 | // - SSD and RAM are examples of storage devices 2 | // - all the apps you run in your computer are stored in RAM 3 | // - RAM is volatile memory, meaning that it loses its contents when the power is turned off 4 | // - Memory management is the process of allocating and deallocating memory in a computer system 5 | 6 | // ****************** JAVASCRIPT WAY ******************************* 7 | // - lets see what happens when we run the code above in javascript 8 | // - so the if we look at the javascript code below 9 | // function add(x, y) { 10 | // const sum = x + y; 11 | // return sum; 12 | // } 13 | // - the sum variable is stored in the stack memory (temporary memory/RAM) and is automatically deallocated when the function returns and is out of scope 14 | // - this is done by the garbage collector in javascript and hence you cant do it manually 15 | // - usually avoids dangling pointers and memory leaks if written correctly 16 | // - this also makes it slow because the garbage collector has to run and clean up the memory 17 | 18 | // ****************** C, C++ WAY ******************************* 19 | // - in c and c++ you have to manually allocate and deallocate memory 20 | // - can lead to memory leaks and dangling pointers if not done correctly because it is not automatically done and it is very easy to make mistakes here 21 | // - learning curve is also high for this 22 | 23 | // ****************** RUST WAY ******************************* 24 | // - Rust uses a concept called ownership to manage memory 25 | // - Ownership is a unique feature of Rust that allows it to manage memory efficiently without the need for a garbage collector 26 | // - make it safe and not very hard to do 27 | // it achieves this by concepts like mutability, ownership model, borrowing and lifetimes 28 | 29 | 30 | pub fn run () { 31 | // memory management concepts 32 | println!("\n#################################"); 33 | println!("Memory Management Module"); 34 | println!("#################################\n"); 35 | 36 | simple_memory_management(); 37 | multiple_functions_mm(); // mm - memory management 38 | dynamic_memory_allocation(); 39 | 40 | mutability(); 41 | } 42 | 43 | // **************************** STACK AND HEAP ******************************* 44 | // - Rust has two types of memory allocation: stack and heap 45 | // - rust stores things like variables and function calls on the stack 46 | // - variables like let u8 = 5; are stored on the stack as it is known at compile time how much space this particular variable is going to take 47 | // but some variables can change at runtime and we dont know how much space they are going to take like a vector or a string 48 | // - these are stored on the heap 49 | // - the heap is less organized than the stack and is slower to access 50 | // - that is why accessing a vector and string is slower than accessing a variable on the stack 51 | // - if you put anything in stack recovering it is easy but if you put it in the heap it is hard to recover it and it takes time because you have to ask the operating system for memory 52 | // - 32bit + 32bit = 64bit would be put in the stack as a stack frame and would be deallocated when the function is done 53 | fn simple_memory_management() { 54 | // stack memory 55 | let _x: i32 = 5; 56 | let _y: i32 = 10; 57 | 58 | } 59 | 60 | // here the first frame for multiple_functions_mm()(64 bit) would be created and then the frame for simple_memory_management()(64 bit) would be created 61 | // both the frames would be put in the stack and would be deallocated when the function is done 62 | // STACK FRAME 63 | // 32 bit + 32 bit = 64 bit (simple_memory_management()) 64 | // 32 bit + 32 bit = 64 bit (multiple_functions_mm()) 65 | // when the execution happens simple_memory_management() would be popped first and then the multiple_functions_mm() 66 | fn multiple_functions_mm() { 67 | let _x: i32 = 5; 68 | let _y: i32 = 10; 69 | simple_memory_management(); 70 | } 71 | 72 | // in this case rust stores the string on heap but to access it we need to use a reference to the string which is stored on the stack 73 | // so the thing on stack that is store looks something like this 74 | // |------------------------------| 75 | // | pointer to heap | address | 76 | // | len | 5 | 77 | // | capacity | 5 | 78 | // |------------------------------| 79 | // the thing the stored on heap looks something likes this 80 | // |--------------| 81 | // |index | value | 82 | // |0 | h | 83 | // |1 | e | 84 | // |2 | l | 85 | // |3 | l | 86 | // |4 | 0 | 87 | // |------|-------| 88 | // when length of the string increases the capacity is increased and the string is moved to a new location in the heap and pointer is updated 89 | // after the string "world" is added the tables above changes with the updated value of world as well 90 | fn dynamic_memory_allocation() { 91 | // heap memory 92 | let s: String = String::from("hello"); 93 | println!("Dynamic Memory Allocation - s: {}", s); 94 | // lets print the capacity and length of the string and the address of the string 95 | println!("Dynamic Memory Allocation - s: capacity: {}", s.capacity()); 96 | println!("Dynamic Memory Allocation - s: length: {}", s.len()); 97 | println!("Dynamic Memory Allocation - s: address: {:p}", s.as_ptr()); 98 | 99 | // lets add a character to the string "world" 100 | let s = s + " world"; 101 | println!("Dynamic Memory Allocation - s: {}", s); 102 | println!("Dynamic Memory Allocation - s: capacity: {}", s.capacity()); 103 | println!("Dynamic Memory Allocation - s: length: {}", s.len()); 104 | println!("Dynamic Memory Allocation - s: address: {:p}", s.as_ptr()); 105 | } 106 | 107 | // ************************* MUTABILITY ***************************** 108 | // - Mutability is the ability to change the value of a variable after it has been declared 109 | // - In Rust, variables are immutable by default, meaning that once a variable is assigned a value, it cannot be changed 110 | // - This helps prevent bugs and makes the code easier to reason about 111 | // - To make a variable mutable, you can use the mut keyword before the variable name 112 | // - knowing when to use mutability is important in Rust, as it can affect the safety and performance of your code 113 | // - language like javascript and python have mutable variables by default even the const keyword in javascript is mutable 114 | // for example 115 | // const x = [1, 2, 3]; 116 | // x.push(4); 117 | // console.log(x); // [1, 2, 3, 4] 118 | // - even tho the the array is const we can update it because the const keyword only makes the reference to the array immutable not the array itself 119 | fn mutability() { 120 | // immutable variable 121 | let x = 5; 122 | println!("Immutable Variable - x: {}", x); 123 | 124 | // mutable variable 125 | let mut y = 10; 126 | println!("Mutable Variable - y: {}", y); 127 | 128 | // change the value of y 129 | y = 15; 130 | println!("Mutable Variable - y: {}", y); 131 | } -------------------------------------------------------------------------------- /concepts/src/concepts/memory_management/borrowing_and_references.rs: -------------------------------------------------------------------------------- 1 | // - In rust borrowing is a way to pass a reference of a value to a function or a variable without transferring the ownership of the value. 2 | // - This is done by using the & symbol before the variable name. 3 | // - The reference is immutable by default, meaning the value cannot be changed. 4 | // - To make the reference mutable, we can use the &mut symbol before the variable name. 5 | // - The reference is valid only till the scope of the variable it is referencing to. 6 | // - The reference is also valid only till the scope of the function it is passed to. 7 | // - you can send a mutable reference to someone aswell without transferring the ownership of the value. But the mutable reference can be only one at a time. 8 | 9 | // Why does rust do this? 10 | // - Rust’s design has been shaped by a few key goals: 11 | // - - Safety: Rust is designed to be safe, and references are one of its key safety features. 12 | // - - Speed: References in Rust are a zero-cost abstraction, meaning that references incur no additional runtime overhead. 13 | // - - Concurrency: Rust’s type system and ownership model ensure that concurrent access to data is data (Race condition) is not possible. 14 | 15 | pub fn run() { 16 | println!("\n#################################"); 17 | println!("Borrowing and References"); 18 | println!("#################################\n"); 19 | 20 | references(); 21 | borrowing(); 22 | borrow_mutable_data(); 23 | } 24 | 25 | fn references() { 26 | let s1 = String::from("hello"); 27 | // s2 has a reference to s1 means it borrows the value of s1 28 | let s2 = &s1; 29 | 30 | println!("s1: {}", s1); 31 | println!("s2: {}", s2); 32 | } 33 | 34 | fn borrowing() { 35 | let s1 = String::from("hello"); 36 | 37 | borrow_variable(&s1); // passes the reference to the function 38 | 39 | // that is why you can still use the s1 as the ownership is not transferred to the function it still belongs to s1 40 | println!("s1: {}", s1); 41 | } 42 | 43 | // you can take a mutable reference to the variable and mutate it by passing the variable as a mutable reference to the function. 44 | // but the mutable reference for a variable can be only one at a time. 45 | // if you try to take another mutable reference to the same variable, it will give an error. 46 | fn borrow_mutable_data() { 47 | let mut s1 = String::from("hello"); 48 | println!("s1 value before: {}", s1); 49 | let s2 = &mut s1; // takes the first mutanle reference for s1 50 | s2.push_str(" world"); // the error below will only show when you take a mutable reference and try to mutate the value 51 | println!("s2 value before: {}", s2); 52 | 53 | // borrow_and_mutate_variable(&mut s1); // this gives error because s1 is already borrowed as mutable reference 54 | // let s3 = &mut s1; // this will give an error because s1 is already borrowed as mutable reference 55 | 56 | println!("Borrowing and mutating the value s1: {}", s1); 57 | } 58 | 59 | // utility that takes the ownership of the string and returns it back if needed. 60 | fn borrow_variable(s: &String) { 61 | println!("Ownership Example - The value of s is {}", s); 62 | } 63 | 64 | // fn borrow_and_mutate_variable(s: &mut String) { 65 | // s.push_str(" world"); 66 | // } 67 | -------------------------------------------------------------------------------- /concepts/src/concepts/memory_management/lifetimes.rs: -------------------------------------------------------------------------------- 1 | // WARNING: This is a very important concept and is very useful in memory management 2 | // In case the example doesnt make much sense or you want to know more about it. 3 | // Please refer to the official documentation here -https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html 4 | // I have used a very naive example to explain the concept in the most easy way possible 5 | 6 | // LIFETIMES 7 | // Lifetimes are a way to tell the compiler how long references are valid for. They are a way to prevent dangling references. 8 | // INFO: This is how you do lifetime annotations in Rust 9 | // &i32 // a reference 10 | // &'a i32 // a reference with an explicit lifetime 11 | // &'a mut i32 // a mutable reference with an explicit lifetime 12 | 13 | // Lifetime annotations don’t change how long any of the references live. Rather, they describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter. 14 | // Lifetime annotations have a slightly unusual syntax: the names of lifetime parameters must start with an apostrophe (') and are usually all lowercase and very short, like generic types. Most people use the name 'a for the first lifetime annotation. We place lifetime parameter annotations after the & of a reference, using a space to separate the annotation from the reference’s typ 15 | 16 | // The answer is that Rust's ownership and borrowing system, including lifetimes, is designed to manage memory safely and efficiently. When you pass references around in a program, Rust needs to ensure that these references are valid for the entire duration they are being used. 17 | // While learning about lifetimes a question arise to me that why do we need lifetimes in the first place? 18 | // If a reference outlives the data it points to, it becomes a dangling reference, leading to undefined behavior and potential security vulnerabilities. 19 | 20 | pub fn run() { 21 | println!("\n#################################"); 22 | println!("Lifetimes"); 23 | println!("#################################"); 24 | 25 | basic_example(); 26 | generic_lifetime(); 27 | static_lifetime(); 28 | } 29 | 30 | // Here, we’ve annotated the lifetime of r with 'a and the lifetime of x with 'b. 31 | // As you can see, the inner 'b block is much smaller than the outer 'a lifetime block. At compile time, 32 | // Rust compares the size of the two lifetimes and sees that r has a lifetime of 'a but that it refers to memory with a lifetime of 'b. 33 | // The program is rejected because 'b is shorter than 'a: the subject of the reference doesn’t live as long as the reference. 34 | // And this avoids dangling pointers and memory leaks 35 | // fn basic_example0() { 36 | // let r; // ---------+-- 'a 37 | // // | 38 | // { // | 39 | // let x = 5; // -+-- 'b | 40 | // r = &x; // | | 41 | // } // -+ | 42 | // // | 43 | // println!("r: {r}"); // | 44 | // } 45 | 46 | fn basic_example() { 47 | let my_string: String = String::from("Hello, world!"); 48 | let mut string_vec: Vec<&String> = vec![]; 49 | 50 | push_string(&mut string_vec, &my_string); 51 | 52 | // drop(my_string); // this will give an error because the reference in the vector is still valid 53 | 54 | println!("stringVec: {:?}", string_vec); 55 | } 56 | 57 | // if we push to the vec in this form it gives an error which is intresting 58 | // fn push_string(str_vec: &mut Vec<&String>, my_string: &String) { 59 | // str_vec.push(my_string); // this gives an error saying that my_string does not live long enough 60 | // } 61 | 62 | // this doesnot give an error so maybe we are not passing the type correctly because the generic 63 | // clearly works here 64 | // fn push_string(str_vec: &mut Vec, my_string: T) { 65 | // str_vec.push(my_string); // this gives an error saying that my_string does not live long enough 66 | // } 67 | 68 | // but what we need to do is to tell the compiler that the reference is valid for a certain lifetime 69 | // so we need to specify the lifetime of the reference 70 | // what this says is that the reference my_string is valid for the lifetime 'a 71 | // and the reference in the vector is also valid for the lifetime 'a 72 | // so the reference in the vector is valid as long as the reference my_string is valid 73 | // so in case the reference my_string goes out of scope the reference in the vector will also be invalid 74 | // hence in situations like this lifetime can help prevent dangling references and is amazing for memory management 75 | fn push_string<'a>(str_vec: &mut Vec<&'a String>, my_string: &'a String) { 76 | str_vec.push(my_string); 77 | } 78 | 79 | fn generic_lifetime() { 80 | let string1 = String::from("abcd"); 81 | let string2 = "xyz"; 82 | let result = longest(string1.as_str(), string2); 83 | println!("The longest string is {}", result); 84 | } 85 | 86 | // utils function to find the longest string 87 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 88 | if x.len() > y.len() { 89 | x 90 | } else { 91 | y 92 | } 93 | } 94 | 95 | // In this case the lifetime of s would be as long as the program run because it has a static lifetime 96 | // and is stored in the read only memory 97 | // before anotating any variable as static make sure it lives for the entire program 98 | fn static_lifetime() { 99 | let s: &'static str = "I have a static lifetime"; 100 | println!("s: {}", s); // prints "I have a static lifetime 101 | } 102 | -------------------------------------------------------------------------------- /concepts/src/concepts/memory_management/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod basics; 2 | pub mod ownership; 3 | pub mod borrowing_and_references; 4 | pub mod lifetimes; -------------------------------------------------------------------------------- /concepts/src/concepts/memory_management/ownership.rs: -------------------------------------------------------------------------------- 1 | // - ownership is a set of rules that governs how a rust programs manages memory. 2 | // - in other languages garbage collectors automatically clean up memory that is no longer being used. In Rust, the memory is managed through a system of ownership with a set of rules that the compiler checks at compile time. None of the ownership features slow down your program while it’s running. 3 | // - if these rules are violated, the program will not compile. 4 | // Rules: 5 | // 1. Each value in Rust has a variable that’s called its owner. 6 | // 2. There can only be one owner at a time. 7 | // 3. When the owner goes out of scope, the value will be dropped. 8 | // - Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance. 9 | 10 | pub fn run() { 11 | println!("\n#################################"); 12 | println!("Ownership and Borrowing"); 13 | println!("#################################\n"); 14 | 15 | stack_variables_ownership(); 16 | heap_ownership_example(); 17 | return_ownership(); 18 | } 19 | 20 | // ownership for stack variables is pretty straightforward. The values are pushed onto the stack and popped off when the function in this particular case will go out of scope. 21 | fn stack_variables_ownership() { 22 | let x = 5; 23 | let y = 2; 24 | let c = x + y; 25 | println!("Stack Variables Ownership - c: {}", c); 26 | } 27 | 28 | // - memory management for heap variables is a bit more complex, that is why common issue like dangling pointer, double free error etc happen commonly. 29 | // - That is why Rust has a system of ownership with a set of rules that the compiler checks at compile time. 30 | // - What happens here is s1 here would be on stack and will point to the value of s1 which is a string on heap. When we assign s1 to s2, the ownership of the value on heap is transferred to s2. So, s1 is no longer valid and will give an error if we try to use it. 31 | // - if in case rust doesnt do this and doesnt complain about it, then it would be a dangling pointer issue. where s1 would be a dangling pointer. 32 | fn heap_ownership_example() { 33 | let s1 = String::from("hello"); 34 | print!("Heap Ownership Example - s1: {}", s1); // this works here because the ownership of s1 is not transferred to s2 35 | let s2 = s1; 36 | 37 | // println!("Heap Ownership Example - s1: {}", s1); // this will give an error because s1 is no longer valid 38 | println!("Ownership Example - s2: {}", s2); 39 | 40 | take_ownership(s2); 41 | // now the ownership of s2 is transferred to take_ownership function. So, s2 is no longer valid here. 42 | // print!("Heap Ownership Example - s2: {}", s2); // this will give an error because s2 is no longer valid 43 | } 44 | 45 | // there are two ways to do this 46 | fn return_ownership() { 47 | let mut s1 = String::from("hello"); 48 | // either create a new variable and take ownership there 49 | let s2: String = take_ownership(s1); 50 | 51 | // or take ownership from the function back and assign it to the same variable by making it a mutable variable. 52 | s1 = take_ownership(s2); 53 | 54 | // or you can also use reference which would be in borrow and refernce file 55 | 56 | println!("Ownership return Example - s1: {}", s1); 57 | } 58 | 59 | // utility that takes the ownership of the string and returns it back if needed. 60 | fn take_ownership(s: String) -> String { 61 | println!("Ownership Example - The value of s is {}", s); 62 | return s; 63 | } 64 | -------------------------------------------------------------------------------- /concepts/src/concepts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod box_smart_pointer; 2 | pub mod datatypes; 3 | pub mod enums; 4 | pub mod error_handling; 5 | pub mod functions; 6 | pub mod generics; 7 | pub mod loops; 8 | pub mod matching; 9 | pub mod option; 10 | pub mod scopes; 11 | pub mod structs; 12 | pub mod traits; 13 | 14 | pub mod memory_management; 15 | 16 | -------------------------------------------------------------------------------- /concepts/src/concepts/option.rs: -------------------------------------------------------------------------------- 1 | // Option enum 2 | // - Option is a generic enum with two variants: Some(T) and None 3 | // - It is used when a value is optional 4 | // - It is similar to null in other languages 5 | // - It is used to handle the absence of a value 6 | // in this example, we say if the value is present, return the value, else return None 7 | 8 | pub fn run() { 9 | println!("\n#################################"); 10 | println!("Option Module"); 11 | println!("#################################\n"); 12 | 13 | option_example(); 14 | } 15 | 16 | fn option_example() { 17 | let result = divide(10, 0); 18 | match result { 19 | Some(value) => println!("Result is {}", value), 20 | None => println!("Error: Cannot divide by zero"), 21 | } 22 | } 23 | 24 | fn divide(x: i32, y: i32) -> Option { 25 | if y == 0 { 26 | return None; 27 | } 28 | Some(x / y) 29 | } -------------------------------------------------------------------------------- /concepts/src/concepts/scopes.rs: -------------------------------------------------------------------------------- 1 | pub fn run() { 2 | println!("\n#################################"); 3 | println!("Scopes Module"); 4 | println!("#################################\n"); 5 | 6 | variable_scope(); 7 | shadowing_example(); 8 | function_scope(); 9 | } 10 | 11 | fn variable_scope() { 12 | { 13 | let x = 5; 14 | println!("Variable Scope - x inside block: {}", x); // x = 5 15 | } 16 | } 17 | 18 | fn shadowing_example() { 19 | let x = 5; 20 | { 21 | let x = x * 2; 22 | println!("Shadowing Example - x in inner scope: {}", x); // x = 10 23 | } 24 | println!("Shadowing Example - x in outer scope: {}", x); // x = 5 25 | } 26 | 27 | fn function_scope() { 28 | let outer_var = 10; 29 | 30 | fn inner_function() { 31 | let inner_var = 20; 32 | println!("Function Scope - Inner variable: {}", inner_var); // inner_var = 20 33 | } 34 | 35 | inner_function(); 36 | println!("Function Scope - Outer variable: {}", outer_var); // outer_var = 10 37 | } 38 | -------------------------------------------------------------------------------- /concepts/src/concepts/structs.rs: -------------------------------------------------------------------------------- 1 | // Structs 2 | // Structs are used to create custom data types. 3 | 4 | struct User { 5 | username: String, 6 | email: String, 7 | sign_in_count: u64, 8 | active: bool, 9 | } 10 | 11 | pub fn run() { 12 | println!("\n#################################"); 13 | println!("Structs"); 14 | println!("#################################\n"); 15 | 16 | struct_example(); 17 | implementing_structs(); 18 | } 19 | 20 | fn struct_example() { 21 | // lets see how the data is created herer 22 | let user1 = User { 23 | // value stored on the heap and reference is stored in the stack 24 | email: String::from("John doe"), 25 | // value stored on the heap and reference is stored in the stack 26 | username: String::from("john@gmail.com"), 27 | // value stored on the stack 28 | active: true, 29 | // value stored on the stack 30 | sign_in_count: 1, 31 | }; 32 | 33 | println!( 34 | "Struct Example - User1 email: {}, username: {}, active: {}, sign_in_count: {}", 35 | user1.email, user1.username, user1.active, user1.sign_in_count 36 | ); 37 | } 38 | 39 | // Implementing Structs 40 | // We can implement methods on structs using impl keyword. 41 | struct Rect { 42 | width: u32, 43 | height: u32, 44 | } 45 | 46 | impl Rect { 47 | fn area(&self) -> u32 { 48 | self.width * self.height 49 | } 50 | fn perimeter(&self) -> u32 { 51 | 2 * (self.width + self.height) 52 | } 53 | } 54 | 55 | fn implementing_structs() { 56 | let rect1 = Rect { 57 | width: 30, 58 | height: 50, 59 | }; 60 | 61 | println!( 62 | "Implementing Structs - The area of the rectangle is: {} square pixels", 63 | rect1.area() 64 | ); 65 | println!( 66 | "Implementing Structs - The perimeter of the rectangle is: {} pixels", 67 | rect1.perimeter() 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /concepts/src/concepts/traits.rs: -------------------------------------------------------------------------------- 1 | // Traits 2 | // A trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior. 3 | // Traits are similar to a feature often called interfaces in other languages, although with some differences. 4 | // Traits are a way to group method signatures together to define a set of behaviors necessary to accomplish some purpose. 5 | 6 | pub fn run() { 7 | println!("\n#################################"); 8 | println!("Traits Module"); 9 | println!("#################################\n"); 10 | 11 | example(); 12 | traist_bounds(); 13 | } 14 | 15 | // EXAMPLE 16 | // We want to make a media aggregator library crate named aggregator that can display summaries of data that might be stored in a NewsArticle or Tweet instance. To do this, we need a summary from each type, and we’ll request that summary by calling a summarize method on an instance. Listing 10-12 shows the definition of a public Summary trait that expresses this behavi 17 | 18 | pub trait Summary { 19 | fn summarize(&self) -> String; 20 | 21 | // we can define default implementation for a trait 22 | fn summarize_default(&self) -> String { 23 | String::from("summary default") 24 | } 25 | } 26 | 27 | pub struct NewsArticle { 28 | pub headline: String, 29 | pub location: String, 30 | pub author: String, 31 | pub content: String, 32 | } 33 | 34 | // implement summary for news article 35 | impl Summary for NewsArticle { 36 | fn summarize(&self) -> String { 37 | format!("{}, by {} ({})", self.headline, self.author, self.location) 38 | } 39 | } 40 | 41 | // we can implement summary for tweet as well but the implementation will be different. 42 | pub struct Tweet { 43 | pub username: String, 44 | pub content: String, 45 | pub reply: bool, 46 | pub retweet: bool, 47 | } 48 | 49 | impl Summary for Tweet { 50 | fn summarize(&self) -> String { 51 | format!("{}: {}", self.username, self.content) 52 | } 53 | fn summarize_default(&self) -> String { 54 | format!("summary default for tweet") 55 | } 56 | } 57 | 58 | fn example() { 59 | let tweet = Tweet { 60 | username: String::from("horse_ebooks"), 61 | content: String::from("of course, as you probably already know, people"), 62 | reply: false, 63 | retweet: false, 64 | }; 65 | 66 | println!("1 new tweet: {}", tweet.summarize()); 67 | // here we have custom implementation for tweet 68 | println!("1 new tweet: {}", tweet.summarize_default()); 69 | 70 | let article = NewsArticle { 71 | headline: String::from("Penguins win the Stanley Cup Championship!"), 72 | location: String::from("Pittsburgh, PA, USA"), 73 | author: String::from("Iceburgh"), 74 | content: String::from( 75 | "The Pittsburgh Penguins once again are the best \ 76 | hockey team in the NHL.", 77 | ), 78 | }; 79 | 80 | println!("New article available! {}", article.summarize()); 81 | // here we have default implementation for article as there were no custom implementation in news article struct 82 | println!("New article available! {}", article.summarize_default()); 83 | } 84 | 85 | fn traist_bounds() { 86 | // We can use trait bounds to specify that a generic type can be any type that has certain behavior. 87 | // Here we are using trait bounds to specify that the item must have a summarize method. 88 | // This is useful when we want to use a trait as a parameter to a function for a parameter type and this ensures that the type has the required behavior. 89 | fn notify(item: &impl Summary) { 90 | println!("Breaking news! {}", item.summarize()); 91 | } 92 | // or we can use the below syntax 93 | // pub fn notify(item: &T) { 94 | // println!("Breaking news! {}", item.summarize()); 95 | // } 96 | 97 | // We can also specify more than one trait bound. Say we wanted notify to use display formatting as well as summarize on item: we specify in the notify definition that item must implement both Display and Summary. We can do so using the + syntax: 98 | // pub fn notify(item: &(impl Summary + Display)) { 99 | // println!("Breaking news! {}", item.summarize()); 100 | // } 101 | // or 102 | // pub fn notify(item: &T) { 103 | // println!("Breaking news! {}", item.summarize()); 104 | // } 105 | 106 | let tweet = Tweet { 107 | username: String::from("horse_ebooks"), 108 | content: String::from("of course, as you probably already know, people"), 109 | reply: false, 110 | retweet: false, 111 | }; 112 | 113 | notify(&tweet); 114 | } 115 | -------------------------------------------------------------------------------- /concepts/src/main.rs: -------------------------------------------------------------------------------- 1 | mod concepts; 2 | 3 | fn main() { 4 | println!("\n#################################"); 5 | println!("Learn rust!"); 6 | println!("#################################"); 7 | 8 | concepts::datatypes::run(); 9 | concepts::scopes::run(); 10 | concepts::loops::run(); 11 | concepts::functions::run(); 12 | concepts::structs::run(); 13 | concepts::enums::run(); 14 | concepts::matching::run(); 15 | concepts::error_handling::run(); 16 | concepts::memory_management::basics::run(); 17 | concepts::memory_management::ownership::run(); 18 | concepts::memory_management::borrowing_and_references::run(); 19 | concepts::memory_management::lifetimes::run(); 20 | concepts::option::run(); 21 | concepts::generics::run(); 22 | concepts::traits::run(); 23 | concepts::box_smart_pointer::run(); 24 | } 25 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "data-structures-and-algorithms" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "data-structures-and-algorithms" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/main.rs: -------------------------------------------------------------------------------- 1 | mod search; 2 | mod sorting; 3 | 4 | fn main() { 5 | // Search Algorithms 6 | search::linear::run(); 7 | search::binary::run(); 8 | 9 | // Sorting Algorithms 10 | sorting::bubble_sort::run(); 11 | sorting::quick_sort::run(); 12 | } 13 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/search/binary.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | 3 | pub fn run() { 4 | println!("############### Binary search in Rust ###############"); 5 | 6 | let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; 7 | let val = 5; 8 | println!("Data: {:?}", data); 9 | println!("Value: {}", val); 10 | 11 | match binary_search(&data, val) { 12 | -1 => println!("Value {} not found", val), 13 | index => println!("Value {} found at index {}", val, index), 14 | } 15 | 16 | println!("Recursice binary search"); 17 | 18 | match binary_search_recursive(&data, val) { 19 | -1 => println!("Value {} not found", val), 20 | index => println!("Value {} found at index {}", val, index), 21 | } 22 | } 23 | 24 | fn binary_search(data: &[i32], val: i32) -> i32 { 25 | let mut low = 0; 26 | let mut high = data.len() - 1; 27 | while low <= high { 28 | let mid = low + (high - low) / 2; 29 | 30 | match data[mid].cmp(&val) { 31 | cmp::Ordering::Equal => return mid as i32, 32 | cmp::Ordering::Less => low = mid + 1, 33 | cmp::Ordering::Greater => high = mid - 1, 34 | } 35 | } 36 | -1 37 | } 38 | 39 | fn binary_search_recursive(data: &[i32], val: i32) -> i32 { 40 | binary_search_recursive_helper(data, val, 0, data.len() as i32 - 1) 41 | } 42 | 43 | fn binary_search_recursive_helper(data: &[i32], val: i32, low: i32, high: i32) -> i32 { 44 | if low > high { 45 | return -1; 46 | } 47 | let mid = low + (high - low) / 2; 48 | match data[mid as usize].cmp(&val) { 49 | cmp::Ordering::Equal => mid, 50 | cmp::Ordering::Less => binary_search_recursive_helper(data, val, mid + 1, high), 51 | cmp::Ordering::Greater => binary_search_recursive_helper(data, val, low, mid - 1), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/search/linear.rs: -------------------------------------------------------------------------------- 1 | pub fn run() { 2 | println!("############### Linear search in Rust ###############"); 3 | 4 | // sample data 5 | let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; 6 | let val = 5; 7 | println!("Data: {:?}", data); 8 | println!("Value: {}", val); 9 | 10 | match linear_search(data, val) { 11 | 0 => println!("Value not found"), 12 | index => println!("Value found at index: {}", index), 13 | } 14 | } 15 | 16 | fn linear_search(data: Vec, val: i32) -> i32 { 17 | for i in 0..data.len() { 18 | if data[i] == val { 19 | return i as i32; 20 | } 21 | } 22 | 23 | // return 0 if not found 24 | 0 25 | } 26 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/search/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod binary; 2 | pub mod linear; 3 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/sorting/bubble_sort.rs: -------------------------------------------------------------------------------- 1 | pub fn run() { 2 | println!("############### Bubble Sort in Rust ###############"); 3 | 4 | let data: Vec = vec![9, 8, 7, 6, 5, 4, 3, 2, 1]; 5 | println!("Unsorted data: {:?}", data); 6 | 7 | let sorted_data = bubble_sort(data); 8 | println!("Sorted data: {:?}", sorted_data); 9 | 10 | println!("Inplace Bubble Sort"); 11 | let data: Vec = vec![9, 8, 7, 6, 5, 4, 3, 2, 1]; 12 | bubble_sort_inplace(&mut data.clone()); 13 | } 14 | 15 | fn bubble_sort(mut data: Vec) -> Vec { 16 | let mut sorted = false; 17 | 18 | while !sorted { 19 | sorted = true; // Assume sorted initially 20 | for i in 0..data.len() - 1 { 21 | if data[i] > data[i + 1] { 22 | data.swap(i, i + 1); 23 | sorted = false; // If we swap, it's not sorted 24 | } 25 | } 26 | } 27 | 28 | data 29 | } 30 | 31 | fn bubble_sort_inplace(data: &mut [i32]) { 32 | let mut n = data.len(); 33 | while n > 1 { 34 | let mut new_n = 0; 35 | for i in 0..n - 1 { 36 | if data[i] > data[i + 1] { 37 | data.swap(i, i + 1); 38 | new_n = i + 1; // Record the last swap position 39 | } 40 | } 41 | n = new_n; // Reduce the range to the last unsorted element 42 | } 43 | println!("Inplace bubble sorted data: {:?}", data); 44 | } 45 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/sorting/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bubble_sort; 2 | pub mod quick_sort; 3 | -------------------------------------------------------------------------------- /data-structures-and-algorithms/src/sorting/quick_sort.rs: -------------------------------------------------------------------------------- 1 | // Quick Sort is a highly efficient divide-and-conquer sorting algorithm. It works by selecting a "pivot" element from the array and partitioning the other elements into two groups: 2 | // 3 | // Elements less than or equal to the pivot. 4 | // Elements greater than the pivot. 5 | // These groups are then sorted recursively. 6 | // 7 | // Steps of Quick Sort 8 | // Choose a pivot: Select an element from the array (commonly the last, first, or a random element). 9 | // Partition the array: Rearrange the array so that elements smaller than the pivot go to its left, and larger elements go to its right. 10 | // Recursively apply quick sort: 11 | // Sort the left subarray. 12 | // Sort the right subarray. 13 | // The recursion ends when the array has one or zero elements, as these are already sorted. 14 | // 15 | // Example 16 | // Given an array [10, 7, 8, 9, 1, 5]: 17 | // 18 | // Choose 5 as the pivot. 19 | // Partition: [1, 5, 7, 8, 9, 10]. 20 | // Recursively sort [1] (left) and [7, 8, 9, 10] (right). 21 | // 22 | // Why Use Quick Sort? 23 | // It's faster than many other algorithms like bubble sort or insertion sort for large datasets. 24 | // Often performs better in practice than merge sort, despite having the same average time complexity. 25 | 26 | pub fn run() { 27 | println!("############### Quick Sort in Rust ###############"); 28 | 29 | let mut data: Vec = vec![9, 8, 7, 6, 5, 4, 3, 2, 1]; 30 | println!("Unsorted data: {:?}", data); 31 | 32 | quick_sort(&mut data); 33 | println!("Sorted data: {:?}", data); 34 | } 35 | 36 | fn quick_sort(data: &mut [i32]) { 37 | if data.len() <= 1 { 38 | return; 39 | } 40 | 41 | let pivot_index = partition(data); 42 | quick_sort(&mut data[0..pivot_index]); 43 | quick_sort(&mut data[pivot_index + 1..]); 44 | } 45 | 46 | fn partition(data: &mut [i32]) -> usize { 47 | let pivot_index = data.len() - 1; 48 | let pivot = data[pivot_index]; 49 | let mut i = 0; 50 | 51 | for j in 0..pivot_index { 52 | if data[j] <= pivot { 53 | data.swap(i, j); 54 | i += 1; 55 | } 56 | } 57 | data.swap(i, pivot_index); 58 | i 59 | } 60 | --------------------------------------------------------------------------------