Today, I took a hands-on approach to learning by evolving a simple rectangle area calculator—from basic variables to a fully structured and idiomatic Rust implementation using structs and methods. This post documents that journey and the concepts I picked up along the way.
📦 Starting Simple: Variables and Functions
We began with the simplest form: separate variables for width and height, and a standalone function to compute area.
fn area(width: u32, height: u32) -> u32 {
width * height
}
While functional, it lacked clarity—nothing tied width and height together logically.
🧳 Refactor 1: Using Tuples
The next step was combining values using a tuple.
let rect = (30, 50);
area(rect)
fn area(dim: (u32, u32)) -> u32 {
dim.0 * dim.1
}
Better grouping—but accessing fields with .0 and .1 isn’t readable or descriptive. Misunderstanding the index can lead to bugs.
🧱 Refactor 2: Introducing Structs
Time to give structure and clarity. Enter the struct:
struct Rectangle {
width: u32,
height: u32,
}
Now, area calculation became more readable:
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.height
}
Structs allow labeling data, making code more intuitive and expressive.
🧮 Debugging Made Easy: Derive Traits
Printing structs directly requires the Debug trait:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
Then we can use:
println!("{:?}", rect);
dbg!(&rect);
The dbg! macro is especially helpful for debugging—prints file name, line number, and the value.
🧪 Method Syntax: Turning Functions Into Methods
Instead of calling area(&rect), we can attach behavior to the struct itself:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
Now, usage is elegant:
rect.area()
Rust implicitly handles references (&self) when calling methods.
📏 More Functional Methods: can_hold()
We added a method to check whether one rectangle can contain another:
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
This adds meaningful domain behavior to our struct—like a mini API.
🏗 Associated Functions
We defined a square() constructor without self, using an associated function:
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
Usage:
let sq = Rectangle::square(10);
Associated functions don’t require a struct instance—they're namespaced under the type.
✂️ Multiple impl Blocks
Rust allows splitting method implementations across multiple impl blocks. This can be useful when separating concerns or trait implementations.
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
🧠 Method Calls = Sugar
rect.area() // syntactic sugar for:
Rectangle::area(&rect)
Rust auto-inserts derefs/borrows to match method expectations.
🔁 Ownership & Methods
- &self: Allows read-only access.
- &mut self: Allows mutation.
- self: Consumes the instance.
For example:
fn set_width(&mut self, width: u32) {
self.width = width;
}
If you try to call this on an immutable struct, the compiler will error out.
💥 Moves Can Be Risky
Methods that take self consume the struct. If you try using it afterward, Rust will throw an error.
let rect = Rectangle { width: 10, height: 10 };
let bigger = rect.max(other); // rect is moved here
println!("{}", rect.area()); // ❌ error
To avoid issues, ensure methods that take self are used thoughtfully.
📋 Bonus: Copy Trait to the Rescue
If your struct only contains primitives, you can make it Copy:
#[derive(Copy, Clone)]
struct Rectangle {
width: u32,
height: u32,
}
Now, calling methods that consume self won’t move the struct—copies are made.
✍️ Summary
Today’s session taught me how Rust allows you to incrementally evolve your program:
- Start with simple functions and variables.
- Use tuples to group values.
- Move to structs for expressiveness.
- Add methods for better readability and structure.
- Handle ownership rules carefully while calling methods.
- Use derived traits for debugging and safety.
- Rust’s combination of clarity, safety, and expressiveness is impressive—and very strict, but for good reasons!
Top comments (0)