【Rust学习】07_2 定义模块来控制作用域与私有性
前言
在本节,我们将讨论模块和其它一些关于模块系统的部分,如允许你命名的 路径(paths);用来将路径引入作用域的 use
关键字;以及变为公有的 pub
关键字。我们还将讨论 as
关键字、外部包和 glob 运算符。
内容
模块速查表
在了解模块和路径的详细信息之前,我们在这里提供了有关模块、路径、use
关键字和 pub
关键字在编译器中如何工作,以及大多数开发人员如何组织代码的快速参考。在本章中,我们将介绍这些规则中的每一个示例,但这是一个很好的参考地方,可以提醒模块是如何工作的。
- 从 crate 根开始:编译 crate 时,编译器首先在 crate 根文件中查找代码(通常为 src/lib.rs 用于库 crate,src/main.rs 用于二进制 crate)以查找要编译的代码。
声明模块:在 crate 根文件中,你可以声明新的模块;假设你用
Mod garden
; 声明了一个 “garden” 模块。编译器将查找 对于这些位置的模块代码:- 内联,在大括号内,替换
mod garden
后面的分号 - 在文件 src/garden.rs 中
- 在文件 src/garden/mod.rs 中
- 内联,在大括号内,替换
声明子模块:在 crate 根以外的任何文件中,你可以声明子模块。例如,您可以在 src/garden.rs 中声明
mod vegetables;
。编译器将在 目录中:- 内联,紧跟在
mod vegetables
之后,在大括号而不是分号内 - 在文件 src/garden/vegetables.rs 中
- 在文件 src/garden/vegetables/mod.rs 中
- 内联,紧跟在
- 模块中代码的路径:一旦模块成为 crate 的一部分,只要隐私规则允许,你就可以使用代码的路径从同一 crate 中的其他任何位置引用该模块中的代码。例如,在 Garden vegetables 模块中可以找到
Asparagus
类型crate::garden::vegetables::Asparagus
。 - 私有与公共:默认情况下,模块中的代码对其父模块是私有的。要将模块设为公共模块,请使用
pub mod
而不是mod
声明它。要将公共模块中的项目也设为公共,请在其声明之前使用pub
。 use
关键字:在范围内,use
关键字创建项目的快捷方式,以减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus
的范围内,您都可以使用use crate::garden::vegetables::Asparagus;
创建快捷方式,从那时起,您只需编写Asparagus
即可在范围内使用该类型。
在这里,我们创建了一个名为 backyard
的二进制 crate 来说明这些规则。crate 的目录(也称为 backyard
)包含这些文件和目录:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
在本例中,crate 根文件是 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 中找到的代码,即:
pub mod vegetables;
这里,pub mod vegetables;
意味着 src/garden/vegetables.rs 中的代码也被包含进来。该代码为:
#[derive(Debug)]
pub struct Asparagus {}
现在让我们深入了解这些规则的细节并在实践中演示它们!
在模块中对相关代码进行分组
模块让我们在一个 crate 中组织代码,以实现可读性和重用性。模块还允许我们控制项目的私有性,因为模块中的代码默认是私有的。私有项目是内部实现细节,不可用于外部使用。我们可以选择将模块和其中的项目设为公共,从而公开它们以允许外部代码使用和依赖它们。
例如,让我们编写一个提供餐厅功能的库 crate。我们将定义函数,但将它们的主体留空,以便专注于代码的组织,而不是餐厅的实现。
在餐饮业中,餐厅的某些部分称为前台,其他部分称为后台。前台是招待顾客的地方,在这里,店主可以为顾客安排座位,服务员接受顾客下单和付款,调酒师会制作饮品。后台则是由厨师工作的厨房,洗碗工的工作地点,以及经理做行政工作的地方组成。
要以这种方式构建我们的 crate,我们可以将其函数组织到嵌套模块中。通过运行 cargo new restaurant --lib
创建一个名为 restaurant
的新库。然后将下方的的代码输入到 src/lib.rs 中,定义一些模块和函数,此代码是前台部分。
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
的模块 一样。模块还可以保存其他项目的定义,例如结构体、枚举、常量、trait 和函数。
通过使用模块,我们可以把相关的定义组织起来,并通过模块命名来解释为什么它们之间有相关性。使用这部分代码的开发者可以更方便的循着这种分组找到自己需要的定义,而不需要通读所有代码。编写这部分代码的开发者通过分组知道该把新功能放在哪里以便继续让程序保持组织性。
前面我们提到了 src/main.rs 和 src/lib.rs 被称为 crate 根。之所以如此命名,是因为这两个文件的内容在模块结构的根部形成了一个名为 crate
的模块,称为模块树,如下所示:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
这个树展示了模块间是如何相互嵌套的(比如,hosting
嵌套在 front_of_house
内部)。这个树还展示了一些模块互为 兄弟 ,即它们被定义在同一模块内(hosting
和 serving
都定义在 front_of_house
内)。继续使用家族比喻,如果模块A包含在模块B的内部,我们称模块A是模块B的 孩子 且模块B是模块A的 父辈 。注意整个模块树的根位于名为 crate
的隐式模块下。
模块树或许让你想起了电脑上文件系统的目录树。这是一个非常恰当的比喻!就像文件系统中的目录那样,你应使用模块来组织你的代码。而且就像一个目录中的文件那样,我们需要一种方法来查找我们的模块。