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)