DEV Community

Subesh Yadav
Subesh Yadav

Posted on

šŸ¦€ Day 6 of #100DaysOfRust: Evolving a Program with Structs & Method Syntax in Rust

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

Now, area calculation became more readable:

fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.height
}
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

Then we can use:

println!("{:?}", rect);
dbg!(&rect);
The dbg! macro is especially helpful for debugging—prints file name, line number, and the value.
Enter fullscreen mode Exit fullscreen mode

🧪 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()
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

āœ‚ļø 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
    }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Method Calls = Sugar

rect.area() // syntactic sugar for:
Rectangle::area(&rect)
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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)