结构体示例程序

为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。

让我们用Scarb创建一个名为 rectangles 的新项目,它获取以像素为单位的长方形的宽度和高度,并计算出长方形的面积。示例5-6显示了位于项目中的 src/lib.cairo 中的小程序,它刚好实现此功能。

文件名: 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
}

示例5-6:通过分别指定长方形的宽和高的变量来计算长方形面积

现在用scarb cairo-run运行该程序:

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

Run completed successfully, returning []

这段代码通过调用每个维度的area函数,成功地算出了矩形的面积,但我们仍然可以修改这段代码来使它的意义更加明确,并且增加可读性。

这段代码的问题在 area 的签名中很明显:

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

area函数应该是计算一个矩形的面积,但是我们写的函数有两个参数,而且在我们的程序中没有任何地方明确说明这些参数的关系。如果把宽度和高度放在一起,会更有可读性,也更容易管理。我们已经在第二章中讨论了一种我们可以做到的方法:使用元组。

使用元组重构

示例5-7显示了我们使用元组的另一个程序版本。

文件名: 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
}

示例5-7:用一个元组指定矩形的宽度和高度

在某种程度上说,这个程序更好一点了。元组帮助我们增加了一些结构性,并且现在只需传一个参数。不过在另一方面,这个版本却有一点不明确了:元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分。

混淆宽度和高度对于计算面积来说并不重要,但是如果我们想计算差值,那就很重要了。我们必须记住 width 是元组索引0height 是元组索引1。如果其他人要使用这些代码,他们必须要搞清楚这一点,并也要牢记于心。很容易忘记或者混淆这些值而造成错误,因为我们没有在代码中传达数据的意图。

使用结构体重构:赋予更多意义

我们使用结构体为数据命名来为其赋予意义。我们可以将我们正在使用的元组转换成一个有整体名称而且每个部分也有对应名字的结构体。

文件名: 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
}

示例 5-8:定义一个Rectangle结构

这里我们定义了一个结构,并将其命名为 Rectangle。在大括号中,我们将字段定义为 widthheight,它们的类型都是 u64。然后,在main中,我们创建了一个Rectangle的特殊实例,它的宽度是30,高度是10。我们的 area函数现在定义了一个名为 rectangle参数,它是Rectangle结构类型。然后我们可以用点符号来访问实例的字段,它给这些值起了描述性的名字,而不是使用01的元组索引值。结构体胜在更清晰明了。

用Trait增加实用功能

在调试程序时打印出 Rectangle 实例来查看其所有字段的值非常有用。示例 5-9 像前面章节那样尝试使用 print。但这并不管用。

文件名: src/lib.cairo

use debug::PrintTrait;

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

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

示例 5-9:试图打印一个 Rectangle实例

当我们编译这段代码时,我们得到了一个错误,有这样的信息:

$ 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.

许多数据类型都实现了 print trait,但 Rectangle 结构没有。我们可以通过在Rectangle上实现PrintTrait trait来解决这个问题,如示例5-10所示。 要了解更多关于traits的信息,请参阅Traits in Cairo

文件名: 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();
    }
}

示例5-10:在Rectangle上实现PrintTrait trait

很好!这不是最漂亮的输出,但它显示了这个实例的所有字段的值,这在调试时肯定会有帮助。

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