📦 Installing Rust To install Rust, use rustup, the official installer. ✅ Installation Steps ``` ``` ``` # For Windows, macOS, or Linux: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` ``` ``` Once installed, verify it with: rustc --version You'll see something like: `rustc 1.72.0 (or similar)` 📁 Creating a New Rust Project Rust uses cargo, its official build tool and package manager. > **cargo new Rust_Basics > cd rust_basics This creates: > rust_basics/ > ├── Cargo.toml > └── src/ > └── main.rs To run your project > cargo run - [ ] Rust Basics 📌 Variables and Mutability In Rust, variables are immutable by default. That means once you assign a value, you can't change it. Immutable variable (default): ``` fn main() { let x = 5; println!("x = {}", x); } ``` Trying to change x would cause an error: x = 6; // ❌ Cannot assign twice to immutable variable Mutable variable: ``` fn main() { let mut x = 5; x = 6; // ✅ This is allowed println!("x = {}", x); } ``` 🔄 Shadowing Rust allows you to declare a new variable with the same name — this is called shadowing. ``` fn main() { let s = "Hello"; let s = "hi"; // shadows the previous `s` println!("{:?}", s); // prints "hi" } ``` NB: Shadowing is not mutation — it's redeclaring a new variable that hides the old one. 🧮 Data Types Rust has two categories of data types: 1. Scalar types: integers, floats, booleans, characters 2. Compound types: tuples and arrays Examples of Scalar Types: let int: i32 = 42; let float: f64 = 3.14; let is_active: bool = true; let letter: char = 'R'; ``` // Scalar types // -single value // - building blocks for more complex types // Integers // -2(**(n-1)) - 2**(n-1) - 1 let i0: i8 = -128; // - 2**(8-1) - 2**(8 - 1) - 1 // - 128 -127 let i1: i16 = 1; let i2: i32 = 1; let i3: i64 = 1; let i4: i128 = 1; let i5: isize = 1; // Signed integers // unsigned integers let u0: u8 = 1; // 0 -2**8 - 1 = 255 let u1: u16 = 1; let u2: u32 = 1; let u3: u64 = 1; let u4: u128 = 1; let u5: usize = 1; // floats let f0: f32 = 0.01; let f1: f64 = 0.01; // boolean let b: bool = true; let c: bool = false; // characters let c: char = 'c'; let e: char = 'f'; // Type conversion let i: i32 = 1; let u: u32 = i as u32; let x: u32 = u + (i as u32); // Min and Max let min_i: i32 = i32::MIN; let max_i: i32 = i32::MAX; println!("i32 min: {min_i}"); println!("i32 max: {max_i}"); let min_char: char = char::MIN; let max_char: char = char::MAX; println!("char min: {min_char}"); println!("char max: {max_char}"); // Overflow let mut u: u32 = u32::MAX; u += 1; println!("overflow u32: {u}"); // checked add - Some(x) | None let mut u = u32::checked_add(u32::MAX, 1); println!("checked add: {:?}", u); // wrapping_add let u = u32::wrapping_add(u32::MAX, 1); println!("wrapping_add: {:?}", u); ``` Tuple: > A tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have a fixed length: once declared, they cannot grow or shrink in size. ``` // Tuple let t: (bool, u32, char) = (true, 1, 'c'); // Destructure let (a, b, c) = t; // ignore with - let (_, b, c) = t; // Empty tuple - unit type let t = (); // Nested tuple let nested = ((1.23, 'a'), (true, 1u32, "told"), ()); println!("nested {} {} {}", nested.0.0, nested.0.1, nested.1.1); // accessing the tuple // u access a tuple by index let t: (bool, u32, char) = (true, 1, 'c'); println!("t = {}, {}, {}", t.0, t.1, t.2); ``` Array: > The Array Type Another way to have a collection of multiple values is with an array. Unlike a tuple, every element of an array must have the same type. Unlike arrays in some other languages, arrays in Rust have a fixed length. ``` // Array // array - fixed length, Known at compile time let arr: [u32; 4] = [1, 2, 3, 4]; println!("arr[2] = {}", arr[2]); // update an element in an array let mut arr: [u32; 5] = [1, 2, 3, 4, 5]; arr[2] = 11; println!("arr[] = {:?}", arr); // declare an array[] and later fill it let arr: [u32; 10] = [0; 10]; println!("{:?}", arr); // slice - length not known at compile time let num: [i32; 10] = [-1, 1, -2, 2, -3, 3, -4, 4, -5, 5]; println!("num = {:?} ", num); // get first 3 elements let s = &num[0..6]; // 0, 1, println!("first 3 elements: {:?} ", s); // middle 4 elements let s = &num[3..7]; println!("middle 4 elements: {:?}", s); // get the last 3 elements let s = &num[7..10]; println!("last 3 element: {:?}", s); // get all elements let s = &num[..]; println!("all elements: {:?} ", s); ``` 🧵 String Types in Rust Rust has two types of strings: &str: a string slice (immutable view) String: an owned, growable, heap-allocated string Why is String heap-allocated for those who might not understand let me explain: The heap is used when data: Can grow/shrink (like String) Is not known at compile time Needs to live beyond the current scope Imagine you have a toy box (that's your computer's memory). Now, you usually keep your favorite toys on a shelf (that’s the stack) easy to grab, but you can only keep small toys there and they must be neatly stacked. But sometimes you have a big toy, like a big stuffed bear. That won’t fit on the shelf. So instead, you put it in a special closet (that’s the heap). When you do that, you write a note and keep it on your shelf that says, “The big bear is in the closet, here’s the key!” That note is called a pointer. In Rust, when something is heap allocated, it means Rust puts that big thing in the closet and keeps a note on your shelf so it knows where to find it later. But Rust is super careful, it wants to make sure nobody forgets to clean up the closet when you’re done with the toy! String example: . 📌 Key Features of String: Mutable unlike string slice (str). Ownable. UTF-8 encoded. Growable ``` // String let msg: String = String::from("Hello Rust"); let len: usize = msg.len(); println!("msg: {msg}"); println!("len: {len}"); ``` ``` let mut msg = String::from("Hello"); msg.push_str(" Rust"); // example of growable! println!("{}", msg); // Hello Rust ``` String Slice (&str) A string slice in Rust is a reference to a part (or all) of a string, rather than owning the string data itself. It’s represented by the type &str (read as “string slice”). 📌 Key Features of String Slices (&str): Immutable by default. Can point to the whole string or just a part of it. Does not own the string data — just references it. UTF-8 encoded like String. let greeting: &str = "Hello, world!"; //Here, "Hello, world!" is a string slice hardcoded into the program, it lives in the program’s binary and is read-only. ``` // str - string slice // &str // - usually used str with reference(borrowed) // - immutable let msg: String = String::from("Hello Rust"); let s: &str = &msg[0..5]; let len: usize = s.len(); println!("slice: {s}"); println!("len: {len}"); // declaring a variable with a string literal // String literal // slice pointing to a specific part of the binary // immutable because hard-coded inside binary // Deref coercion let s: &str = "Hello"; let s: &str = "hi"; println!("{:?}", s); let s: &str = r#" { "name": "John", "age": 30, "city": "New York", "b": {"c": 3} } "#; println!("{:#}", s); ``` 🔁 From &str to String You convert a string literal (&str) to a String using: let owned = String::from("text"); //by default "text" is &str but by adding the ::from it becomes a string hence ownable and growable // or let owned = "text".to_string(); by adding ".to_string" it becomes a string fully ownable and growable. Why is this so? Because "text" is a &'static str, not a String. Rust keeps string literals in read-only memory, to get a mutable version, you must convert it. 🔡 String Slices (&str) Explained ``` fn stringSlice() { // mutable String let mut msg: String = String::from("Hello Rust"); msg.push('!'); // Shadowing let msg = msg.len(); // msg is now a number, not a string println!("Length of message: {}", msg); } ``` 🌑 What is Shadowing? In Rust, you can declare a new variable with the same name as an existing one. This is called shadowing ``` let x = 5; let x = x + 1; // This doesn't mutate x — it creates a NEW x, shadowing the previous one. let s: &str = "Hello"; // First binding of `s` let s: &str = "hi"; // New binding, shadowing the first `s` You aren’t mutating s; you are redefining it. The old value is now inaccessible, and s now refers to "hi". Pls keep note of this. ``` So the question why is shadowing different from mutation: 🔒 Why Is This Different From Mutation? Rust does not allow you to mutate an immutable variable, but it does allow you to shadow it with a new one. Mutation would look like this: ``` let s = "Hello"; s = "hi"; // ❌ ERROR! `s` is immutable To mutate, you must declare the variable using the keyword mut: let mut s = "Hello"; s = "hi"; // ✅ Now it's allowed ``` ``` // Add &str to String String manipulation let mut msg: String = "Hello".to_string(); msg += "!"; println!("{}", msg); What’s going on? "Hello".to_string() creates a heap-allocated String from a string literal. "Hello" is a &str, and .to_string() turns it into a String. msg += "!" is shorthand for: msg = msg + "!"; Here, Rust allows you to add a &str ("!") to a String (msg). Internally, this works because Rust implements Add<&str> for String. This mutates msg to become "Hello!". Hence The result: Hello! ``` ``` // sting interpolation let lang = "Rust"; let emoji = "🦀"; let msg = format!("Hello {lang} {emoji}"); println!("{}", msg); 🔍 What’s going on? format! is a macro in Rust similar to println!, but instead of printing to the console, it returns the formatted string. Inside the string: {lang} automatically inserts the value of the variable lang. {emoji} does the same with emoji. So this: format!("Hello {lang} {emoji}") becomes: String::from("Hello Rust 🦀") Hence The result: Hello Rust 🦀 NB: by default both {lang} & {emoji} are string slice &str but when inserted: let msg = format!("Hello {lang} {emoji}"); it becomes a String. ``` ``` // Deref coercion Deref coercion is Rust's way of converting a reference like &String into a &str (or other target types) automatically by calling deref() behind the scenes. This makes code cleaner and more intuitive. let msg: String = String::from("Hello Rust"); let s: &str = &msg; println!("{}", s); ``` 🧠 Summary | Feature | Description | | -------------- | --------------------------------------- | | `let` | Declares a variable | | `mut` | Makes it mutable | | Shadowing | Re-declares variable with same name | | `&str` | String slice, fixed and immutable | | `String` | Growable, heap-allocated string | | `String::from` | Converts `&str` to `String` | | Comments | Document your code with `//` or `/* */` | Struct > In Rust, a struct (short for structure) is a custom data type that lets you group related values into a single cohesive unit. Structs are used to create more complex data models than just primitive types (like i32, f32, etc.). 🔧 What is a struct? A struct defines the shape of data—what pieces of data it holds, and how they relate. It’s like creating your own type. ``` struct User { username: String, email: String, age: u32, } You can then create instances of this struct and access or modify the fields. ``` 🧱 Types of Structs in Rust Rust provides 3 main types of structs: 1. Named-field Structs Most common form—each field has a name and type. ``` struct Person { name: String, age: u32, } ``` Usage: ``` let p = Person { name: String::from("Alice"), age: 30, }; println!("{} is {} years old", p.name, p.age); ``` 2. Tuple Structs Like a tuple, but with a name. Fields don’t have names—just types and positions. ``` struct Color(u8, u8, u8); ``` Usage: ``` let red = Color(255, 0, 0); println!("Red: {}, {}, {}", red.0, red.1, red.2); ``` 3. Unit-like Structs || empty structs Have no fields at all. Useful as markers or for traits. ``` struct Logger; ``` Usage: ``` let _log = Logger; ``` methods in Struct 🧠 What Are Struct Methods? A method is a function that's associated with a struct (or any type). It’s defined using an impl block, which stands for implementation. Methods can: Access or modify data inside the struct. Perform calculations. Provide utility behavior for instances of the struct. > 🛠️ How to Define Methods for a Struct ``` struct Circle { radius: f64, } impl Circle { // Method: calculates the area of the circle fn area(&self) -> f64 { 3.14159 * self.radius * self.radius } // Method: returns true if radius is above a certain value fn is_large(&self) -> bool { self.radius > 10.0 } } Explanation: impl Circle defines methods for the Circle struct. &self is a reference to the instance (like this in other languages). self can also be passed by value (self), or as a mutable reference (&mut self). ``` . 🧪 Using the Methods ``` fn main() { let c = Circle { radius: 5.0 }; println!("Area = {}", c.area()); // Calls area method println!("Is large? {}", c.is_large()); // Calls is_large method } ``` 📦 Associated Functions (No self) You can also define functions without self. These are usually constructors like new(). ``` impl Circle { fn new(radius: f64) -> Self { Circle { radius } } } Usage: let c = Circle::new(8.0); ``` | Feature | Description | | --------------------- | ----------------------------------- | | `impl` block | Implements methods for a struct | | `&self` | Read access to the struct | | `&mut self` | Mutable access | | `self` | Takes ownership of the instance | | `fn new(...) -> Self` | Common convention to create structs | Enums- Enumerator Nb: This article provides a comprehensive guide to getting started with Rust. It covers installation, project creation, variables, mutability, shadowing, data types, and string handling, along with practical examples. Perfect for beginners, this guide explains core Rust concepts clearly and concisely, helping you confidently build your first programs.