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 debuggingprints 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 dont require a struct instancethey'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)