---
title: "10.9 Project: rectangles"
description: "A program built one compiling step at a time: from loose variables to a tuple to a struct with methods, watching each refactor make the code clearer."
url: "https://learnrust.net/chapter-10/project-rectangles/"
last_updated: "2026-06-13"
---

# 10.9 Project: rectangles

Time to use the whole chapter at once. We'll write a small program that computes the area of a rectangle, then refactor it three times, watching it get clearer at every step. This is the Rust Book's classic rectangle arc, run the learncpp way: it compiles after every change, so you could stop at any point and have a working program. The goal isn't the area (that's one multiplication). It's feeling *why* each refactor is an improvement.

## Step 1: loose variables

The most direct version uses two separate numbers:

```rust
fn area(width: u32, height: u32) -> u32 {
    width * height
}

fn main() {
    let width = 30;
    let height = 50;
    println!("The area is {} square pixels.", area(width, height));
}
```

```
The area is 1500 square pixels.
```

It works, and there's already something wrong with it. `area` takes two parameters that have nothing to say they belong together. Nothing stops a caller from passing the height first, or passing the width of one rectangle and the height of another. The function signature `area(width: u32, height: u32)` doesn't express the one fact that matters: these two numbers describe *one* rectangle. This is lesson [10.1](https://learnrust.net/chapter-10/introduction-to-program-defined-types/)'s complaint, live.

## Step 2: a tuple

We can at least bundle the two numbers so they travel as one value. A tuple (lesson [4.11](https://learnrust.net/chapter-4/tuples-and-the-unit-type/)) does that:

```rust
fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

fn main() {
    let rect = (30, 50);
    println!("The area is {} square pixels.", area(rect));
}
```

```
The area is 1500 square pixels.
```

Better in one way: `area` now takes a single argument, and `rect` is one thing you pass around. But we traded a problem for a different one. Which is the width, `.0` or `.1`? The tuple doesn't say. For a rectangle the symmetry almost saves us, but imagine drawing it on screen: if you swap the dimensions you'd stretch it the wrong way and the compiler couldn't care less, because `(u32, u32)` has lost the labels. We added grouping but threw away meaning.

## Step 3: a struct

A struct gives us both: one value *and* named parts. This is the version a Rust programmer writes:

```rust
struct Rectangle {
    width: u32,
    height: u32,
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("The area is {} square pixels.", area(&rect));
}
```

```
The area is 1500 square pixels.
```

Now the signature tells the truth. `area(rectangle: &Rectangle)` says "I take one rectangle, by reference, and only look at it." `rectangle.width` and `rectangle.height` are unmistakable; there's no slot number to remember. And we pass `&rect`, borrowing (lesson [9.1](https://learnrust.net/chapter-9/references-borrowing-a-value/)), so `main` keeps owning `rect` and could compute its perimeter on the next line. We've recovered the meaning the tuple lost, and kept the grouping the loose variables lacked.

## Step 4: make it printable

Suppose we want to print the rectangle itself, not just its area. From lesson [10.7](https://learnrust.net/chapter-10/deriving-debug/), that needs `Debug`:

```rust
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("rect is {:?}", rect);
    println!("pretty:\n{:#?}", rect);
}
```

```
rect is Rectangle { width: 30, height: 50 }
pretty:
Rectangle {
    width: 30,
    height: 50,
}
```

One line, `#[derive(Debug)]`, and the rectangle prints with its field names attached. Worth it for the debugging alone.

## Step 5: area becomes a method

The `area` function only ever makes sense for a `Rectangle`, so it belongs *on* `Rectangle` (lesson [10.5](https://learnrust.net/chapter-10/methods-and-impl-blocks/)). We move it into an `impl` block and turn its parameter into `&self`:

```rust
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width >= other.width && self.height >= other.height
    }
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    let small = Rectangle { width: 10, height: 40 };

    println!("rect is {:?}", rect);
    println!("its area is {} square pixels.", rect.area());
    println!("can rect hold small? {}", rect.can_hold(&small));
}
```

```
rect is Rectangle { width: 30, height: 50 }
its area is 1500 square pixels.
can rect hold small? true
```

`rect.area()` reads as "the area of rect," subject before verb, and `area` is now grouped with the type it serves. We threw in `can_hold` to show a method taking another rectangle: it borrows both `self` and `other` immutably, changing neither. Both methods take `&self` because both only read, the lesson [10.5](https://learnrust.net/chapter-10/methods-and-impl-blocks/) default.

## Step 6: a constructor

Finally, give `Rectangle` a `new` and a `square` so callers don't repeat the struct literal (lesson [10.6](https://learnrust.net/chapter-10/associated-functions-and-constructors/)):

```rust
impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    fn square(side: u32) -> Rectangle {
        Rectangle { width: side, height: side }
    }

    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    let sq = Rectangle::square(20);
    println!("rect area {}, square area {}", rect.area(), sq.area());
}
```

```
rect area 1500, square area 400
```

Notice `new` uses field init shorthand (lesson [10.3](https://learnrust.net/chapter-10/field-init-and-update-syntax/)) because its parameters already carry the field names. `square` makes the "all sides equal" intent obvious at the call site, which is the whole reason to give a second constructor a descriptive name.

That's the arc: loose numbers with no relationship, to a tuple with grouping but no labels, to a struct with both, to a type that owns its data, prints itself, and carries its own behavior. Every Rust program of any size is built out of this move, repeated.

## Quiz time

**Question #1**

Add a method `is_square(&self) -> bool` to `Rectangle` and call it on `Rectangle::square(20)` and `Rectangle::new(30, 50)`.

<details class="solution">
<summary>Show solution</summary>

```rust
impl Rectangle {
    fn is_square(&self) -> bool {
        self.width == self.height
    }
}

fn main() {
    println!("{}", Rectangle::square(20).is_square());  // true
    println!("{}", Rectangle::new(30, 50).is_square()); // false
}
```

It only compares fields, so `&self` is right. (Comparing two `u32`s with `==` is lesson [6.4](https://learnrust.net/chapter-6/comparison-operators-and-float-equality/); no float-equality worries here because these are integers.)

</details>

**Question #2**

In step 3, why is `area(rectangle: &Rectangle)` better than the tuple version `area(dimensions: (u32, u32))`, even though both take one argument?

<details class="solution">
<summary>Show solution</summary>

The struct version keeps the *labels*. `rectangle.width` says what it is; the tuple's `dimensions.0` makes you remember which slot was the width, and nothing stops a caller from building the tuple in the wrong order. Both group the data into one value, but only the struct preserves the meaning of each part.

</details>

**Question #3**

Why does `area` take `&self` rather than `self`? What would break if it took `self`?

<details class="solution">
<summary>Show solution</summary>

`area` only reads the fields, so it borrows with `&self`. If it took `self`, calling `rect.area()` would *consume* `rect` (lesson [8.6](https://learnrust.net/chapter-8/ownership-and-functions/)), and you couldn't use `rect` again afterward, including printing it or calling another method. Reading methods take `&self` so the caller keeps the value.

</details>

One chapter, one type, built up from nothing. Next is the summary and a quiz that exercises the whole chapter, and then chapter 11 introduces the other half of Rust's type-building toolkit: enums, where a value is *one of* several shapes, and `match` finally gets the power lesson [7.3](https://learnrust.net/chapter-7/introduction-to-match/) only hinted at.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
