An Example Program Using Structs

Para entender cuándo podríamos usar estructuras, escribamos un programa que calcule el área de un rectángulo. Comenzaremos usando variables individuales y luego reescribiremos el programa hasta que estemos usando estructuras en su lugar.

Let’s make a new project with Scarb called rectangles that will take the width and height of a rectangle specified in pixels and calculate the area of the rectangle. Listing 5-6 shows a short program with one way of doing exactly that in our project’s src/lib.cairo.

Filename: src/lib.cairo

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
}

Listing 5-6: Calculating the area of a rectangle specified by separate width and height variables

Now run the program with scarb cairo-run:

$ scarb cairo-run
[DEBUG] ,                               (raw: 300)

Run completed successfully, returning []

Este código logra calcular el área del rectángulo llamando a la función area con cada dimensión, pero podemos hacer más para que este código sea claro y legible.

El problema con este código es evidente en la declaración de la función area:

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

The area function is supposed to calculate the area of one rectangle, but the function we wrote has two parameters, and it’s not clear anywhere in our program that the parameters are related. It would be more readable and more manageable to group width and height together. We’ve already discussed one way we might do that in Chapter 2: using tuples.

Refactoring with Tuples

Listing 5-7 shows another version of our program that uses tuples.

Filename: src/lib.cairo

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
}

Listing 5-7: Specifying the width and height of the rectangle with a tuple

En cierto modo, este programa es mejor. Las tuplas nos permiten agregar un poco de estructura y ahora estamos pasando solo un argumento. Pero en otro sentido, esta versión es menos clara: las tuplas no nombran sus elementos, por lo que tenemos que indexar las partes de la tupla, lo que hace que nuestro cálculo sea menos obvio.

Mezclar el ancho y la altura no importaría para el cálculo del área, pero si queremos calcular la diferencia, ¡sería importante! Tendríamos que tener en cuenta que width es el índice de tupla 0 y height es el índice de tupla 1. Esto sería aún más difícil de entender y tener en cuenta para otra persona si usara nuestro código. Debido a que no hemos transmitido el significado de nuestros datos en nuestro código, ahora es más fácil introducir errores.

Refactoring with Structs: Adding More Meaning

Usamos estructuras para agregar significado al etiquetar los datos. Podemos transformar la tupla que estamos usando en una estructura con un nombre para el todo y nombres para las partes.

Filename: src/lib.cairo

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
}

Listing 5-8: Defining a Rectangle struct

Aquí hemos definido una estructura y la hemos llamado Rectangle. Dentro de las llaves, definimos los campos como width y height, los cuales tienen el tipo u64. Luego, en main, creamos una instancia particular de Rectangle que tiene un ancho de 30 y una altura de 10. Nuestra función area ahora está definida con un parámetro, al que hemos llamado rectangle que es de tipo de la estructura Rectangle. Luego podemos acceder a los campos de la instancia con notación de punto, y dar nombres descriptivos a los valores en lugar de usar los valores de índice de tupla de 0 y 1.

Adding Useful Functionality with Trait

It’d be useful to be able to print an instance of Rectangle while we’re debugging our program and see the values for all its fields. Listing 5-9 tries using the print as we have used in previous chapters. This won’t work.

Filename: src/lib.cairo

use debug::PrintTrait;

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

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

Listing 5-9: Attempting to print a Rectangle instance

Cuando compilamos este código, obtenemos un error con el siguiente mensaje:

$ cairo-compile src/lib.cairo
error: Method `print` not found on type "../src::Rectangle". Did you import the correct trait and impl?
 --> lib.cairo:16:15
    rectangle.print();
              ^***^

Error: Compilation failed.

The print trait is implemented for many data types, but not for the Rectangle struct. We can fix this by implementing the PrintTrait trait on Rectangle as shown in Listing 5-10. To learn more about traits, see Traits in Cairo.

Filename: src/lib.cairo

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();
    }
}

Listing 5-10: Implementing the PrintTrait trait on Rectangle

¡Bien! No es el resultado más bonito, pero muestra los valores de todos los campos para esta instancia, lo que definitivamente ayudaría durante la depuración.

Last change: 2023-09-20, commit: cbb0049