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

Abir DuttaAbir Dutta
8 min read

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

  1. Each value in Rust has a single owner

  2. There can only be one owner at a time

  3. 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):

  1. You can have either ONE mutable borrow OR multiple immutable borrows

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

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