Today was a big milestone in my Rust journey. I spent the day reviewing the foundational ownership system and then moved into one of Rust’s most powerful features—structs. Below is a detailed recap and breakdown of everything I learned, along with practical examples.
🔁 Ownership Recap
Rust’s ownership model ensures memory safety without a garbage collector. Let’s look at how it compares to garbage collection and revisit key ownership principles.
🔍 Ownership vs Garbage Collection
Languages like Python and Java use garbage collection (GC), where memory is freed by a background process. In contrast, Rust deallocates memory predictably at compile time through ownership and borrowing.
In Rust, each piece of heap data must have a single owner. When the owner goes out of scope, the memory is freed.
This eliminates GC pauses and unexpected mutations like in this Python example:
d = Document(words)
d2 = Document(d.get_words())
d2.add_word("world")
# This also mutates `d` unintentionally.
Rust avoids this by requiring explicit references and ownership transfer.
🧠 Ownership: How Rust Thinks
🗃️ Stack vs Heap
Stack: Stores fixed-size data like numbers and pointers.
Heap: Stores growable data like String and Vec.
Example:
let a_box = Box::new(42);
let ref_stack = &a_box; // Points to stack
let ref_heap = &*a_box; // Points to heap
🧾 Slices
A slice like &s[0..3] lets you reference part of a collection without taking ownership. These are “fat pointers” (contain length + pointer).
🧮 Ownership at Compile-Time
Rust uses permissions:
- R (read)
- W (write)
- O (own)
Example of move (loses ownership):
let s = String::from("hello");
let s2 = s; // Ownership moved to s2
println!("{}", s); // ❌ Error: s no longer valid
📌 Borrowing
Immutable reference (&T) removes write/own from original.
Mutable reference (&mut T) removes all access from the original.
✅ Valid:
let mut s = String::from("Hello");
let r = &s; // read-only
println!("{}", r);
❌ Invalid:
let r1 = &mut s;
let r2 = &s; // ❌ Can't borrow as immutable while mutably borrowed
🧨 Use-after-free & Double Free
Rust prevents undefined behavior like:
Using a pointer after its memory is freed
Freeing memory twice
📦 Structs: Custom Data Types
Coming from JavaScript, I’m very familiar with objects — key-value pairs like:
const user = {
username: "subesh",
email: "me@mail.com",
active: true
};
In Rust, structs serve a similar purpose. They let you group related data under a single type, but in a strongly typed, memory-safe way.
✅ Basic Struct Definition
This is how you define a struct in Rust:
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
It’s like a typed version of a JS object, where each field has a clear type.
🛠 Creating Instances
You can now create values of the User type:
let user1 = User {
username: String::from("user1"),
email: String::from("test@testmail.com"),
active: true,
sign_in_count: 1,
};
Rust enforces ownership here too — values like String are moved into the struct unless cloned.
🧾 Field Access & Mutation
You can read fields using dot notation, and update them if the struct is mutable:
println!("{}", user1.email);
let mut user2 = user1;
user2.email = String::from("new@mail.com");
This is very JS-like in syntax but with the added safety of the compiler checking types and ownership.
✨ Struct Update Syntax
You can also create new structs by reusing existing ones:
let user2 = User {
username: String::from("newuser"),
..user1
};
Rust will move the values that aren't copied (like String), so user1 becomes partially invalid after this unless its fields are Copy.
🪄 Tuple Structs
Tuple structs look like this:
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
println!("{}", black.0);
They’re great when the meaning is clear by position — similar to using arrays in JS for unnamed data.
💠 Unit-like Structs
These are structs without fields. They’re useful when you just want to implement traits or act as markers:
struct AlwaysEqual;
let a = AlwaysEqual;
🔐 Field Ownership in Structs
This is where Rust differs from JS. In JavaScript, fields can be strings, numbers, or even references to other objects without worrying about ownership. In Rust, each field needs to either own its data or borrow it with a lifetime.
Prefer using String in structs:
struct User {
username: String,
}
Over this:
struct User {
username: &str, // ❌ Needs lifetime annotation
}
🕹 Borrowing Fields
Rust lets you borrow individual fields, but prevents simultaneous conflicting borrows:
struct Point { x: i32, y: i32 }
let mut p = Point { x: 1, y: 2 };
let x = &mut p.x;
// Can't use p.y while x is borrowed mutably
This might feel restrictive coming from JS, but it avoids hard-to-debug bugs.
📝 Summary
- Rust avoids garbage collection using ownership.
- Borrowing rules (with permissions R, W, O) ensure memory safety.
- Structs in Rust are like strongly-typed JS objects, but safer.
- You can define named structs, tuple structs, and unit structs.
- Borrowing and mutating fields inside structs follows strict rules.
- Prefer owned types like String over borrowed ones unless using lifetimes explicitly.
That’s all for Day 5. Tomorrow, I’ll dive into methods and enums—building more expressive and safe Rust programs.
Let me know what you're learning too! 🚀
Follow my journey on Twitter @subeshyadav
Top comments (0)