定义控制作用域和隐私的模组
Defining Modules to Control Scope and Privacy
在本节中,我们将讨论模组和模组系统的其他部分,即允许咱们给项目取名的 路径,paths、将路径引入作用域的 use
关键字,以及将项目构造为公开的 pub
关键字等。我们还将讨论 as
关键字、外部包和 glob
操作符。
首先,我们将以一份方便咱们今后组织咱们代码的规则清单开始。然后,我们将详细解释每一条规则。
模组速查表
Modules Cheat Sheet
这里我们提供了有关模组、路径、use
关键字和 pub
关键字,在编译器中如何运作,以及大多数开发人员如何组织代码的快速参考。我们将在本章节中,逐个讨论这些规则的示例,但这是个很到的,作为模组工作原理的提醒的地方。
- 从代码箱根开始:编译代码箱时,编译器首先会在代码箱根文件(通常是库代码箱的
src/lib.rs
,或二进制代码箱的src/main.rs
)中,查找要编译的代码;
-
声明模组:在代码箱根文件中,咱们可以声明出新的模组;比如,咱们以
mod garden;
,声明一个 ”花园“ 模组。编译器将在下面这些地方,查找该模组的代码:- 内联式,在替换
mod garden
后分号的花括号内; - 文件
src/garden.rs
中; - 文件
src/garden/mod.rs
中。
- 内联式,在替换
-
声明子模组:在除代码箱根外的任何文件中,都可以声明子模组。例如,咱们可能会在
src/garden.rs
中,声明mod vegetables;
。编译器将在下面这些地方的父模组目录中,查找该子模组的代码:- 内联式,直接跟在
mod vegetables
之后,代替分号的花括号内; - 文件
src/garden/vegetables.rs
中; - 文件
src/garden/vegetables/mod.rs
中。
- 内联式,直接跟在
-
模组中代码的路径:一旦某个模组成为咱们代码箱的一部分,只要隐私规则允许,咱们就可以在同一代码箱的任何其他地方,使用代码的路径来引用该模组中的代码。例如,花园蔬菜模组中的
Asparagus
(芦笋)类型,就可以在crate::garden::vegetables::Asparagus
处找到。 -
私有与公开:模组内的代码默认对其父模组是私有的。要将某个模组构造为公开,可使用
pub mod
代替mod
声明该模组。要将某个公开模组中的项目,也构造为公开,请在其声明前使用pub
。 -
use
关键字:在作用域中,use
关键字会创建出到项目的快捷方式,以减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus
的作用域中,咱们都可以使用use crate::garden::vegetables::Asparagus;
创建出一个快捷方式,并在那以后,咱们就只需写下Asparagus
,就可以在该作用域中使用这个类型。
在此,我们创建了一个说明这些规则的名为 backyard
的二进制代码箱。这个代码箱的目录也称为 backyard
,其中包含下面这些文件和目录:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
本例中的代码箱根文件是 src/main.rs
,他包含:
文件名:src/main.rs
use crate::garden::vegetables::Asparagus; pub mod garden; fn main() { let plant = Asparagus {}; println! ("I'm growing {:?}!", plant); }
pub mod garden;
这行告诉编译器,要包含其在 src/garden.rs
中找到的代码,即:
文件名:src/garden.rs
#![allow(unused)] fn main() { pub mod vegetables; }
在这里,pub mod vegetables;
表示也包含了 src/garden/vegetables.rs
中的代码。这些代码是:
#![allow(unused)] fn main() { #[derive(Debug)] pub struct Asparagus {} }
现在就来进入到这些规则的细节,并在实际操作中对他们进行演示吧!
把有关联的代码分组在模组中
Grouping Related Code in Modules
模组,modules 让我们可以把代码组织在代码箱中,以提高可读性并方便重复使用。模组还允许我们控制程序项目的私密性,因为某个模组内的代码默认为私有。私有项目是一些内部的实现细节,不供外部使用。我们可以选择将模组及其中的项目公开,这样就可以让外部代码使用和依赖他们。
例如,我们来编写一个提供餐厅功能的库代码箱。我们将定义函数的签名,但函数体留空,以专注于代码的组织,而不是实现餐厅的实现。
在餐饮业,餐厅的一些部分被称为 前厅,front of house,其他部分被称为 后厨,back of house。前厅是顾客所在的地方;包括餐厅领台为顾客安排座位、服务员接受点单和付款,以及调酒师调制饮料的地方。后厨是厨师和厨工在厨房工作、洗碗工进行清洁和经理进行行政工作的地方。
为了以这种方式架构我们的代码箱,我们可以将其函数,组织成一些嵌套的模组。请运行 cargo new restaurant --lib
,创建一个名为 restaurant
的新库;然后在 src/lib.rs
中,输入下面清单 7-1 中的代码,定义一些模组和函数签名。下面是前台部分:
文件名:src/lib.rs
#![allow(unused)] fn main() { 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() {} } } }
清单 7-1:包含着其他包含了函数模组的 front_of_house
模组
我们使用后跟模组名字(本例中为 front_of_house
)的 mod
关键字,定义某个模组。随后模组的主体,位于花括号内。在模组内部,我们可以放置其他模组,就像本例中 hosting
和 serving
模组。模组还可以包含其他项目的定义,如结构体、枚举、常量、特质,以及如同清单 7-1 中的函数。
经由模组的使用,就可以将有关联的一些定义,组织在一起,并以他们因何相关而取个名字。使用此代码的程序员们,就可以根据这些分组,而非通读全部的这些定义,来浏览代码,那么在找到他们想要使用的那些定义时,就会容易一些。而对于要往该代码增加新功能的那些程序员,就清楚在哪里放置代码,来保持程序组织有序。
前面我们曾提到,src/main.rs
和 src/lib.rs
被称为代码箱根。之所以叫这两个文件为代码箱根,是因为这两个文件的内容,构成了位于该代码箱结构(即 模组树,module tree)根部,名为 crate
的模组。
清单 7-2 显示了清单 7-1 中结构的模组树。
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
清单 7-2:清单 7-1 中代码的模组树
该树显示了其中一些模组是如何嵌套在另一模组中的;例如,hosting
嵌套在 front_of_house
中。该树还显示其中一些模组彼此属于 同辈,siblings,即他们被定义在同一模组中;hosting
和 serving
是 front_of_house
中定义的同辈份模组。如果模组 A 包含在模组 B 中,我们就说模组 A 是模组 B 的子模组,而模组 B 是模组 A 的父模组。请注意,整个模组树的根,都位于名为 crate
的隐式模组下。
模组树可能会让咱们联想到电脑上的文件系统目录树;这是个非常恰当的比较!就像文件系统中的目录一样,咱们可以使用模组来组织代码。而就像目录中的文件一样,我们需要一种找到咱们模组的方法。