Learning Cairo - Optimizing Code Structure Using Structs (9)

Learning Cairo - Optimizing Code Structure Using Structs (9)

Prerequisite

Install cairo-run.

Struct Example

In Learning Cairo - The Syntax of Structures (8), we have seen some Cairo programs using structs. In this article, let's see an example of optimizing code structure using structs.

Suppose we need a program to calculate the area of a rectangle, then we can easily write this code:

use debug::PrintTrait;
fn main() {
    let width1 = 30;
    let height1 = 10;
    let area = area(width1, height1);
    area.print();
}

fn area(width: u64, height: u64) -> u64 {
    width * height
}

The area function receives two arguments, width and height, calculates their product, and returns it. This code is fine, it does the job, but maybe we can improve it.

First, when we call the area function, we need to remember the position of the two parameters, for example, we need to remember that the first parameter is width and the second is height (of course, in this area function, it doesn't matter if the arguments are reversed). Second, we need to manually pass in all the parameters. If we need to calculate the area of a more complex shape, there may be many parameters, making it hard to read and manage.

We learned about the Tuple structure in previous articles, and we can try to refactor the code using Tuple:

use debug::PrintTrait;
fn main() {
    let rectangle = (30, 10);
    let area = area(rectangle);
    area.print(); // print out the area
}

fn area(dimension: (u64, u64)) -> u64 {
    let (x, y) = dimension;
    x * y
}

We put both width and height into a tuple, which makes the area function only take one parameter. For more complex parameters, we can also put them into a tuple to optimize parameter complexity. However, this brings another problem, the parameters of area currently have no meaning, it's just a tuple, and its elements are not named. This makes it even less readable. When using the area function, we not only need to remember the order of the parameters, but also have to carefully align the function parameters because the parameters have no names.

So let's refactor the code using structs:

use debug::PrintTrait;

struct Rectangle {
    width: u64,
    height: u64,
}

fn main() {
    let rectangle = Rectangle { width: 30, height: 10,  };
    let area = area(rectangle);
    area.print(); // print out the area
}

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

We defined a struct of type Rectangle, with elements width and height. Now in the area function, we can pass a variable of type Rectangle and directly access its elements using dot notation to calculate the product.

This way of writing is clear and simple. First, we only need to pass a struct when passing parameters. Second, the struct type explicitly indicates that it is a rectangle. Finally, in the area function, we can clearly know that its function is to calculate the area of the rectangle through the width and height of the rectangle.

Let's consider a problem. We put width and height in the Rectangle struct. If we want to know their values, we need to print the Rectangle struct:

use debug::PrintTrait;

struct Rectangle {
    width: u64,
    height: u64,
}

fn main() {
    let rectangle = Rectangle { width: 30, height: 10,  };
    rectangle.print();
}

However, this code will report an error:

error: Method `print` not found on type "main::main::Rectangle". Did you import the correct trait and impl?
 --> main.cairo:10:15
    rectangle.print();
              ^***^

The error message suggests that the Rectangle type does not implement the print trait. We know that many types have implemented the print trait, such as u256, u64, so they can use the .print() calling method. But the Rectangle type has not implemented the print trait. We can manually implement the print trait for the Rectangle type:

use debug::PrintTrait;

struct Rectangle {
    width: u64,
    height: u64,
}

fn main() {
    let rectangle = Rectangle { width: 30, height: 10,  };
    rectangle.print();
}

impl RectanglePrintImpl of PrintTrait<Rectangle> {
    fn print(self: Rectangle) {
        self.width.print();
        self.height.print();
    }
}

After implementing the print trait, we can use rectangle.print() to print the data of type Rectangle.

Conclusion

In this article, we learned about the use cases of structs, how using structs can optimize code writing and readability, and how we can implement various traits for structs to accelerate program development.

Low latency and free Starknet node awaits!

For a limited time, Reddio is offering unrestricted access to its high-speed StarkNet Node, completely free of charge. This is an unparalleled opportunity to experience the fastest connection with the lowest delay. All you need to do is register an account on Reddio at https://dashboard.reddio.com/ and start exploring the limitless possibilities.

You can discover why Reddio claims the fastest connection by reading more here.