定义模块以控制作用域
在本节,我们将讨论模块和其它一些关于模块系统的部分,如允许你命名项的 路径(paths);用来将路径引入作用域的use
关键字。
首先,我们将从一系列的规则开始,在你未来组织代码的时候,这些规则可被用作简单的参考。接下来我们将会详细的解释每条规则。
模块小抄
这里我们提供一个简单的参考,用来解释模块、路径、 use
关键词如何在编译器中工作,以及大部分开发者如何组织他们的代码。我们将在本章节中举例说明每条规则,不过这是一个解释模块工作方式的良好参考。你可以用scarb new backyard
创建一个新的Scarb项目来跟随。
-
从 crate 根节点开始:当编译一个 crate, 编译器首先在 crate 根文件(src/lib.cairo)中寻找要编译的代码。
-
声明模块::在 crate 根文件中,你可以声明新的模块; 例如,你用
mod garden;
声明一个 “garden”模块。编译器会在下列路径中寻找模块代码:-
内联,在大括号中,当
mod garden
后方不是一个分号而是一个大括号// crate root file (src/lib.cairo) mod garden { // code defining the garden module goes here }
-
-
在文件 src/garden.cairo 中
-
声明子模块:在除了 crate 根节点以外的其他文件中,你可以定义子模块。例如,你可以在以下文件中声明
mod vegetables;
。 src/garden.cairo 。编译器会在以父模块命名的目录中寻找子模块代码:-
内联,直接跟在
mod vegetables
后面,用大括号代替分号// src/garden.cairo file mod vegetables { // code defining the vegetables submodule goes here }
-
在文件 src/garden/vegetables.cairo 中
-
-
模块中的代码路径:一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的
Asparagus
类型可以在backyard::garden::vegetables::Asparagus
被找到。 -
use
关键字:在一个作用域内,use
关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用backyard::garden::vegetables::Asparagus
的作用域,你可以通过use backyard::garden::vegetables::Asparagus;
创建一个快捷方式,然后你就可以在作用域中只写Asparagus
来使用该类型。
这里我们创建一个名为backyard
的crate 来说明这些规则。该 crate 的路径同样命名为backyard
,该路径包含了这些文件和目录:
backyard/
├── Scarb.toml
└── src
├── garden
│ └── vegetables.cairo
├── garden.cairo
└── lib.cairo
在这种情况下,crate根文件是 src/lib.cairo ,它包含:
文件名: src/lib.cairo
use garden::vegetables::Asparagus; mod garden; fn main() { let Asparagus = Asparagus {}; }
mod garden;
行告诉编译器包括它在 src/garden.cairo 中发现的代码是:
文件名: src/garden.cairo
mod vegetables;
这里,mod vegetables;
意味着 src/garden/vegetables.cairo 中的代码也被包括在内。这段代码是:
#[derive(Copy, Drop)]
struct Asparagus {}
这行use garden::vecants::Asparagus;
让我们把Asparagus
类型带入作用域、所以我们可以在main
函数中使用它。
现在让我们深入了解这些规则的细节并在实际中演示它们!
在模块中对相关代码进行分组
模块 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。 作为一个例子,让我们写一个crate,提供一个餐馆的机能。我们将定义函数的签名,但将其主体留空,以专注于代码的组织,而不是餐馆的实现。
在餐饮业,餐馆中会有一些地方被称之为 前台(front of house),还有另外一些地方被称之为 后台(back of house)。 前台是招待顾客的地方,在这里,店主可以为顾客安排座位,服务员接受顾客下单和付款,调酒师会制作饮品。 后台则是由厨师工作的厨房,洗碗工的工作地点,以及经理做行政工作的地方组成。
我们可以将函数放置到嵌套的模块中,来使我们的 crate 结构与实际的餐厅结构相同。
通过运行 scarb new restaurant
创建一个名为 restaurant
的新包;然后将示例7-1中的代码输入 src/lib.cairo ,以定义一些模块和函数签名。这里是前台的部分:
文件名: src/lib.cairo
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
我们定义一个模块,是以 mod
关键字为起始,然后指定模块的名字(本例中叫做 front_of_house
),并且用花括号包围模块的主体。在模块内,我们还可以定义其他的模块,就像本例中的 hosting
和 serving
模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、以及列表中6-1中展示的函数。
通过使用模块,我们可以将相关的定义分组到一起,并指出他们为什么相关。程序员可以通过使用这段代码,更加容易地找到他们想要的定义,因为他们可以基于分组来对代码进行导航,而不需要阅读所有的定义。 程序员向这段代码中添加一个新的功能时,他们也会知道代码应该放置在何处,可以保持程序的组织性。
在前面我们提到了,src/lib.cairo 叫做 crate 根。之所以这样叫它是因为这个文件的内容在 crate 模块结构的根组成了一个名为 crate 的模块,该结构被称为 模块树( module tree )。
示例7-2显示了示例7-1中结构的模块树。
restaurant
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
这个树展示了一些模块是如何被嵌入到另一个模块的(例如,hosting
嵌套在 front_of_house
中)。这个树还展示了一些模块是互为 兄弟( siblings )的,这意味着它们定义在同一模块中( hosting
和 serving
被一起定义在 front_of_house 中)。继续沿用家庭关系的比喻,如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的 子( child ),模块 B 则是模块 A 的 父( parent )。注意,整个模块树都植根于名为 restaurant
crate的隐式模块下。
这个模块树可能会令你想起电脑上文件系统的目录树;这是一个非常恰当的类比!就像文件系统的目录,你可以使用模块来组织你的代码。并且,就像目录中的文件,我们需要一种方法来找到模块。