Started Deep Diving into Rust Today - Here's What Blew My Mind (Part 1)

Table of contents
- Why am I even learning Rust ??
- The Basics That Made Me Go "Ohhh Now I Get It"
- The Mind-Blowing Part - No Null, Just Options and Results
- The Big Question - How Does Rust Manage Memory ??
- Moving vs Cloning - The Memory Dance
- Borrowing - The Solution to the Moving Problem
- The Borrowing Rules - Rust's Safety Net
- What I Learned Today - The Big Picture
- Something to Note

8th August, 2025
Started deep diving into rust today, got to know many exciting things to be honest, lets just start one by one. I've been hearing about Rust everywhere - from backend engineers flexing about memory safety to systems programmers claiming it's the future. So I thought, why not give it a shot?
After spending the whole day with Rust, I realized this language is like that strict but caring teacher who makes you follow rules but ensures you never mess up badly. Let me walk you through what I discovered, step by step, because trust me, there's a lot to unpack here :))
But first, let me ask you something...
Why am I even learning Rust ??
You know the drill by now - I always start with the "why" question. For me, it was simple: I wanted to understand why everyone's going crazy about memory safety and performance. Plus, I'm tired of dealing with segmentation faults in C and garbage collection overhead in other languages. Rust promises the best of both worlds, so let's see if it delivers!
The Basics That Made Me Go "Ohhh Now I Get It"
Let me start with the fundamentals that completely changed how I think about programming...
Loops, Mutables, and Conditionals - The Foundation
Okkay first things first, Rust has the usual suspects but with a twist. Here's what caught my attention:
fn main() {
// Basic loop - nothing fancy yet
let mut counter = 0;
loop {
counter += 1;
println!("Counter: {}", counter);
if counter == 5 {
break; // Exit the loop
}
}
// For loop - cleaner way
for i in 1..6 {
println!("For loop counter: {}", i);
}
// While loop with condition
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
}
Notice that mut
keyword? Yeah, that's Rust being strict about mutability. By default, everything is immutable unless you explicitly say "hey, I want to change this later" with mut
. Coming from JavaScript where everything is mutable by default, this felt weird at first, but now I see the beauty - no accidental modifications!
Structs and Enums - Building Blocks That Actually Make Sense
Now here's where things get interesting. Structs in Rust are like blueprints for your data, and enums... oh boy, enums are on steroids!
// Struct - think of it as a custom data type
struct User {
name: String,
email: String,
age: u32,
active: bool,
}
// Enum - this is where Rust shows off
enum MessageStatus {
Sent,
Delivered,
Read,
Failed(String), // Look! It can hold data too
}
fn main() {
// Creating a struct instance
let user1 = User {
name: String::from("Abir"),
email: String::from("abir@example.com"),
age: 25,
active: true,
};
// Using enum
let msg_status = MessageStatus::Failed(String::from("Network timeout"));
println!("User: {}, Age: {}", user1.name, user1.age);
}
The cool part about Rust enums is that they can hold different types of data. That Failed(String)
variant can store the error message - how neat is that?
The Mind-Blowing Part - No Null, Just Options and Results
This is where I literally went "WHATTTT" out loud. Rust doesn't have null! Instead, it has this beautiful thing called Option<T>
for handling missing values, and Result<T, E>
for handling errors. Let me show you why both of these are genius:
fn find_user_by_id(id: u32) -> Option<String> {
// Simulating a database lookup
if id == 1 {
Some(String::from("Abir")) // User found
} else {
None // User not found - no null pointer exceptions!
}
}
fn main() {
let user = find_user_by_id(1);
// Pattern matching with match - this is beautiful!
match user {
Some(name) => println!("Found user: {}", name),
None => println!("User not found"),
}
// Or use if let for simpler cases
if let Some(name) = find_user_by_id(2) {
println!("User exists: {}", name);
} else {
println!("Nope, user doesn't exist");
}
}
Results - Error Handling That Actually Makes Sense
But wait, there's more! What about functions that can fail? Instead of throwing exceptions or returning error codes, Rust uses Result<T, E>
- it's like Option but for success/failure scenarios:
use std::fs::File;
use std::io::ErrorKind;
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Cannot divide by zero!")) // Error case
} else {
Ok(a / b) // Success case
}
}
fn main() {
// Handling Result with match
let result = divide(10.0, 2.0);
match result {
Ok(value) => println!("Division result: {}", value),
Err(error) => println!("Error: {}", error),
}
// You can also use unwrap_or for default values
let safe_result = divide(10.0, 0.0).unwrap_or(-1.0);
println!("Safe result with default: {}", safe_result);
// Or use the ? operator for error propagation (more on this later!)
}
The beauty here is that you CAN'T ignore errors - if a function returns Result<T, E>
, you must handle both the Ok
and Err
cases. No more silent failures or forgotten error checking!
This pattern matching with match
is like a switch statement but way more powerful. You HAVE to handle all cases - no forgetting to check for null or errors and getting runtime crashes!
The Big Question - How Does Rust Manage Memory ??
Coming from languages with garbage collectors, I was curious: how does Rust know when to clean up memory? Does it have a garbage collector running in the background?
The answer blew my mind: NO GARBAGE COLLECTOR!
Rust uses something called "ownership" - it's like having a very strict librarian who tracks exactly who has which book and when they need to return it. Here's how it works:
Ownership Rules - The Holy Trinity
Each value in Rust has a single owner
There can only be one owner at a time
When the owner goes out of scope, the value is dropped (memory freed)
fn main() {
let s1 = String::from("hello"); // s1 owns this string
let s2 = s1; // Ownership moves from s1 to s2
// println!("{}", s1); // ERROR! s1 no longer owns the string
println!("{}", s2); // This works fine
// s2 goes out of scope here, memory is automatically freed
}
This is called "moving" - the ownership literally moves from one variable to another. No copying happening in the background, no memory leaks, no garbage collector needed!
Moving vs Cloning - The Memory Dance
Now here's the interesting part. Sometimes you want to actually copy the data, not just move ownership. That's where cloning comes in:
fn print_string(s: String) {
println!("{}", s);
// s goes out of scope here and is dropped
}
fn main() {
let original = String::from("Hello, Rust!");
// Option 1: Move ownership (original becomes invalid)
// print_string(original);
// println!("{}", original); // ERROR! original was moved
// Option 2: Clone the data (more memory, but both variables work)
print_string(original.clone());
println!("Original still works: {}", original);
// Option 3: Borrow (we'll talk about this next!)
}
Borrowing - The Solution to the Moving Problem
But wait, what if you don't want to move OR clone? What if you just want to let a function peek at your data without taking ownership? Enter borrowing:
fn print_string_borrow(s: &String) { // &String means "borrow a String"
println!("{}", s);
// No ownership, so nothing gets dropped here
}
fn modify_string(s: &mut String) { // Mutable borrow
s.push_str(" - Modified!");
}
fn main() {
let mut my_string = String::from("Hello");
// Immutable borrow - function can read but not modify
print_string_borrow(&my_string);
// Mutable borrow - function can modify
modify_string(&mut my_string);
println!("Final string: {}", my_string); // Still works!
}
The &
symbol means "borrow this value" - it's like lending someone your book but you still own it. They can read it (immutable borrow with &
) or even write notes in it (mutable borrow with &mut
), but they have to give it back!
The Borrowing Rules - Rust's Safety Net
Here's where Rust gets really strict (but for good reasons):
You can have either ONE mutable borrow OR multiple immutable borrows
Borrows must always be valid (no dangling pointers)
fn main() {
let mut s = String::from("hello");
let r1 = &s; // Immutable borrow
let r2 = &s; // Another immutable borrow - this is fine
println!("{} and {}", r1, r2); // Both immutable borrows used here
let r3 = &mut s; // Mutable borrow - this is fine because r1 and r2 are done
r3.push_str(" world");
println!("{}", r3);
}
This prevents data races at compile time! No more mysterious bugs from concurrent modifications.
What I Learned Today - The Big Picture
After diving deep into Rust today, I realized it's not just another programming language - it's a completely different way of thinking about memory management and safety. The ownership system might feel restrictive at first, but it's like training wheels that prevent you from crashing.
The coolest part? All these safety checks happen at compile time. Once your Rust code compiles, you can be pretty confident it won't crash with memory errors. That's powerful!
Something to Note
Don't get overwhelmed by all these rules! I spent the first few hours fighting the borrow checker (Rust's compiler that enforces these rules), but once you understand the "why" behind each rule, it starts making perfect sense. Think of it as having a really smart code reviewer who never gets tired of catching your mistakes :))
Also, if you're coming from garbage-collected languages, the ownership concept might feel weird. That's normal! Give it time, practice with small examples, and soon you'll start appreciating the control and performance it gives you.
Hope I was able to share something valuable from my Rust learning journey today! The language definitely lives up to the hype, and I can see why systems programmers are falling in love with it.
Happy Coding! By Abir
Subscribe to my newsletter
Read articles from Abir Dutta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Abir Dutta
Abir Dutta
I am a Blockchain and MERN stack developer. While building real-life application based full-stack projects, I also like to connect and network with different types of people to learn more from them.