📦 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.