Rust ownership and memory management rules diagram

My First Encounter with Ownership



Introduction

In the world of low-level programming, C has long been the gold standard, offering developers total freedom to manage memory manually. However, with great power comes great responsibility, and often, great frustration. Manual management is a double-edged sword that frequently leads to critical issues like memory leaks, dangling pointers, and segmentation faults. Rust enters the scene with a revolutionary promise: providing the performance and control of a systems language while eliminating these memory safety bugs entirely, all without the overhead of a Garbage Collector (GC). It achieves this through its most unique feature: Ownership.

What is Ownership?

At its core, Ownership is a set of rules that governs how a Rust program manages memory. Unlike languages with a GC that scans memory at runtime, or languages like C where you manually malloc and free, Rust checks these rules at compile time. If the rules are broken, the program won’t even build.

The Owner

In Rust, every value (like a string, an integer, or a struct) is bound to a variable. That variable is called the owner of the value. The owner is responsible for cleaning up memory once it’s no longer needed.




The 3 Rules


Rule 1: Every value has an owner

In Rust, memory isn’t just “floating” around. Every piece of data is tied to a specific variable name. This variable is responsible for that data.

Rule 2: There can only be one owner at a time

This is where Rust differs most from other languages. To prevent double-free errors, a common bug in C where you try to free the same memory twice:

// double-free bug in C
int main() {
    char *s1 = malloc(10 * sizeof(char)); // Allocate memory on the heap
    strcpy(s1, "Hello");

    char *s2 = s1; // s2 is just a copy of the pointer address

    free(s1); // Memory is freed
    free(s2); // ❌ BUG: Double Free! The program will crash.

    return 0;
}

Rust ensures that if you assign one variable to another, the ownership moves:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // Ownership MOVES from s1 to s2

    println!("{}", s1);    // ❌ ERROR: s1 is no longer the owner!
    println!("{}", s2);    // ✅ WORKS: s2 is the new owner.
}

Rule 3: When the owner goes out of scope, the value is dropped

You don’t need to call free() or delete(). When a variable’s scope ends, Rust automatically calls a special function called drop to clean up the memory.

fn main() {
    {
        let s = String::from("hello"); // s enters scope, do something with s
    } // s goes OUT of scope here, the memory for "hello" is automatically freed by Rust.

    println!("{}", s); // ❌ ERROR: s is gone.
}



Ownership and Functions: The Borrowing System

When you pass a variable to a function, you have three main ways to handle its ownership and data. This is where the concept of Borrowing comes into play.

  • Taking Ownership (The Move): If you pass a variable into a function without any special symbols, the ownership moves into the function. After the function ends, the variable is dropped and you can no longer use it in the original scope.
  • Immutable Reference (Borrowing): If you only need to read the data, you can pass a reference using &. This is called “Immutable Borrowing”, you cannot change the data
  • Mutable Reference (Mutable Borrowing): If you need to modify the data without taking ownership, you use &mut. Crucial Rule: You can only have one active mutable reference to a piece of data at a time to prevent data races.

What’s Next?

This is just the beginning of my journey with Rust. My ultimate goal is to apply my knowledge of Network Engineering to build a high-performance HTTP server from scratch. Stay tuned for more posts as I move from basic syntax to networking and systems architecture!

Separator

Want to work with me?

Let's build something together!

Connect

Get in touch

I'll get back to you as soon as I can.