Hey everyone! 👋
I just wrapped up Week 1 of my journey to learn fundamental of the Rust programming language from scratch and it’s been a ride and documented weekly right here on Dev.to. My goal is to not just learn the language, but to openly share my struggles, breakthroughs, and how I'm leveraging the power of AI in this journey in 2025.
Here’s what I learned, what confused me, how I got unstuck, and what I’m building next. Without further ado, let's unpack what I learned and how I'm navigating Rust's unique challenges.
1. Variables
Coming from Python, variables in Rust felt familiar at first, but with a crucial twist: immutability by default. This was the first hint of Rust's philosophy: safety and predictability.
fn main() {
let x = 5; // Immutable by default
println!("The value of x is: {}", x);
// x = 6; // This would cause a compile-time error!
// println!("The value of x is: {}", x);
let mut y = 10; // Explicitly mutable
println!("The value of y is: {}", y);
y = 12;
println!("The new value of y is: {}", y);
// Shadowing is cool too!
let z = "hello";
let z = z.len(); // z is now 5
println!("The value of z is: {}", z);
}
The mut
keyword is a constant reminder of Rust's safety guarantees. No accidental modifications here! And shadowing
, that's a neat way to reuse variable names with a new value and type.
2. Basic Types (Numbers, Chars, Bools, Unit)
Rust's strong typing was another significant shift. Explicitly defining integer sizes (i32
, u64
), understanding floating-point types (f32
, f64
), and working with char
and bool
felt very robust.
The ()
unit type, which signifies "no value," is also a simple but powerful concept, especially when thinking about functions that don't return anything.
fn main() {
// Numbers
let int_32: i32 = 42;
let float_64: f64 = 3.14;
// Char, Bool, Unit
let single_char: char = 'R';
let is_learning: bool = true;
let unit_value = (); // This is the unit type
println!("Int: {}, Float: {}", int_32, float_64);
println!("Char: {}, Bool: {}", single_char, is_learning);
println!("Unit value: {:?}", unit_value); // Need Debug trait to print unit
}
Statements vs. Expressions
This was a concept I'd encountered before, but Rust truly hammers it home. Statements perform actions but don't return values (e.g., variable declarations). Expressions evaluate to a value (e.g., a function call, a block of code). This makes if
blocks or even entire function bodies act as expressions, which is super powerful!
fn main() {
let y = 6; // This is a statement
let x = { // This entire block is an expression
let y = 3;
y + 1 // No semicolon means it's an expression
};
println!("The value of x is: {}", x); // x will be 4
}
3. Functions
Functions in Rust are quite straightforward, with explicit type annotations for parameters and return values. This compile-time check helps catch errors early.
fn add_numbers(x: i32, y: i32) -> i32 {
x + y // This is an expression, equivalent to `return x + y;`
}
fn greet(name: &str) { // No return value, implicitly returns unit ()
println!("Hello, {}", name);
}
fn main() {
let sum = add_numbers(5, 7);
println!("The sum is: {}", sum);
greet("Rustacean");
}
4. Ownership: My First Rust "Aha!" (and Head-Scratcher) Moment
This is where Rust truly differentiates itself, and honestly, where I spent the bulk of my time this week. Ownership is Rust's core concept for memory safety without a garbage collector. It dictates how data is managed in memory.
The three rules of ownership:
- Each value in Rust has a variable that's called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
Initially, this felt very restrictive, especially when working with String
s. The concept of "moves" where ownership is transferred, rather than data being copied, was fascinating and challenging.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2, s1 is no longer valid!
// println!("s1: {}", s1); // This would cause a compile-time error!
println!("s2: {}", s2);
let s3 = s2.clone(); // Deep copy, both s2 and s3 are valid
println!("s2: {}, s3: {}", s2, s3);
}
My AI-Assisted Ownership Breakdown
When I first hit this, my brain twisted. I turned to my AI coding assistant using GitHub Copilot Chat.
My Prompt
Explain Rust ownership (moves, drops, scope) to me as simply as possible, using an analogy from daily life, and then provide a simple code example that demonstrates a move.
AI's Analogy (Paraphrased)
Think of a library book. Only one person can check out a book at a time (one owner). When you return the book (owner goes out of scope), it's available again (dropped). If you lend your book to someone else, you no longer have it (moved).
This analogy, combined with the AI-generated code example, really helped solidify the concept for me. It wasn't perfect, but it gave me a strong starting point to then dive deeper into "The Book."
5. References and Borrowing
If ownership defines who has the data, references and borrowing are how you temporarily access that data without taking ownership. This is crucial for passing data to functions without having to copy it or lose ownership.
The key rules of borrowing:
- At any given time, you can have either one mutable reference or any number of immutable references.
- References must always be valid.
This "one mutable OR many immutable" rule is Rust's magic for preventing data races at compile time. It's strict, but it makes the code incredibly safe.
fn calculate_length(s: &String) -> usize { // Takes a reference
s.len()
}
fn change_string(some_string: &mut String) { // Takes a mutable reference
some_string.push_str(", world!");
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Pass a reference
println!("The length of '{}' is {}", s1, len); // s1 is still valid!
let mut s2 = String::from("hello");
change_string(&mut s2); // Pass a mutable reference
println!("The modified string is: {}", s2);
// This would cause a compile-time error:
// let r1 = &mut s2;
// let r2 = &mut s2; // Cannot have two mutable references at once!
}
Understanding of Memory(Heap vs. Stack)
A big piece that helped me grasp ownership was understanding where data lives in memory.
- Stack: This is where fixed-size data lives, and it's fast! Things like integers, booleans, and fixed-size arrays go here. Data is pushed and popped in a strict order.
- Heap: This is for data whose size isn't known at compile time, or that might change. String
is a great example, its text content lives on the heap, while the String
struct itself (pointer, length, capacity) lives on the stack. Accessing heap data is slower because you have to follow a pointer.
This distinction is crucial for ownership. When you move a String
, you're effectively moving the ownership of the data on the heap, not necessarily copying the actual large data. This prevents multiple pointers to the same heap data, eliminating common memory bugs like double-free errors.
Learning by Doing: Rust by Pactice
eyond just reading, I spent a good amount of time working through the interactive exercises on RUST BY PRACTICE website.
This resource was incredibly helpful for immediately applying the concepts I was learning. Being able to modify and run small snippets directly in the browser solidified my understanding of:
- How different data types behave.
- The practical implications of mutability.
- Seeing ownership and borrowing errors in action and figuring out how to fix them.
I highly recommend it as a companion to "The Rust Programming Language" book. It truly helps bridge the gap between theory and practice.
Learning resources I found useful so far for the fundamentals include;
Week 1 Reflections
This first week has been an intense introduction. Rust's compiler can feel like a strict teacher, but every error message is a lesson. Ownership, references, and borrowing, along with the underlying memory model of the Heap and Stack, are definitely the most challenging parts, but I can already see the immense power they offer in terms of memory safety and performance.
My AI assistant has been invaluable for getting quick explanations and analogies, acting as a personal tutor when "The Book" felt a bit too dense on a particular point. However, it reinforced that my active understanding is paramount; the AI provides guidance, but the learning and critical evaluation are entirely human.
Follow me on my favorite place here.
See ya in week 2.
Top comments (0)