开发环境
- Windows 10
- Rust 1.61.0
- VS Code 1.68.1
项目工程
这里继续沿用上次工程rust-demo
定义控制范围和隐私的模块
在这一节中,我们将讨论模块和模块系统的其他部分,即允许您命名项目的路径paths;将路径带入范围的use关键字;和pub关键字来公开项目。我们还将讨论as关键字、外部包和glob操作符。
首先,我们将从一个规则列表开始,以便在您将来组织代码时参考。然后我们将详细解释每一个规则。
模块快速参考
下面是模块、路径、use关键字和pub关键字在编译器中的工作方式,以及大多数开发人员如何组织他们的代码。我们将浏览每一个规则的例子,但是这是一个很好的地方来回顾模块是如何工作的。
- 从箱(crate)根开始:编译一箱crate时,编译器首先查看板条箱根文件(通常对于库箱crate是src/lib.rs,对于二进制箱crate是src/main.rs)。
- 声明模块:在箱crate根文件中,你可以用mod garden声明一个名为“garden”的新模块;。编译器将在以下位置查找模块内部的代码:
- 内联,直接跟在mod garden后面,在花括号内而不是分号内
- 在src/garden.rs文件中
- 在src/garden/mod.rs文件中
- 声明子模块:在除了作为箱crate一部分编译的箱crate根目录之外的任何文件中(例如,src/garden.rs),您可以声明子模块(例如,mod vegetables).编译器将在以父模块命名的目录中的以下位置查找子模块中的代码:
- 内联,直接跟在mod vegetables后面,在花括号内,而不是分号内
- 在src/garden/vegetables.rs文件中
- 在src/garden/vegetables.rs文件中
- 模块中代码的路径:一旦一个模块被编译成你的箱crate的一部分,只要隐私规则允许,你就可以从这个箱crate中的任何地方通过使用路径crate::garden::vegetables::Asparagus来引用那个模块中的代码(例如,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,包含:
文件名:src/main.rs
use crate::garden::vegetables::Asparagus; // use关键字
pub mod garden; // pub关键字声明模块garden
fn main() {
let plant = Asparagus {};
println!("I'm growing {:?}!", plant);
}
pub mod garden:意味着编译器包含它在src/garden.rs中找到的代码,即:
文件名:src/garden.rs
pub mod vegetables; // 声明模块vegetables是公共的
pub mod vegetables:表示src/garden/vegetables.rs中的代码也包括在内:
#[derive(Debug)]
pub struct Asparagus {}
将相关代码分组到模块中
模块让我们将一个箱crate中的代码组织成组,以便于阅读和重用。模块还控制项目的隐私,即项目是否可以由外部代码使用(public)或者是内部实现细节而不能供外部使用(private)。
举个例子,让我们写一个提供餐馆功能的图书馆箱crate。我们将定义函数的签名,但将它们的主体留空,以便专注于代码的组织,而不是用代码实际实现一个餐馆。
在餐饮业中,餐馆的一些部分被称为前厅,另一些被称为后厅。房子的前面是顾客所在的地方;这里是主人招待顾客、服务员接受订单和付款、调酒师调酒的地方。房子的后面是厨师和厨师在厨房工作的地方,洗碗工打扫卫生,经理做行政工作。
为了像真正的餐馆一样构建我们的箱crate,我们可以将功能组织成嵌套的模块。通过运行cargo new - lib restaurant创建一个名为restaurant的新库;然后把下述的代码放到src/lib.rs中,定义一些模块和函数签名。
文件名: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。模块也可以保存其他项目的定义,比如结构、枚举、常量、特征或者——如上例所示——函数。
通过使用模块,我们可以将相关的定义组合在一起,并命名它们为什么相关。使用这种代码的程序员将更容易找到他们想要使用的定义,因为他们可以根据组来导航代码,而不必通读所有的定义。向代码中添加新功能的程序员会知道将代码放在哪里以保持程序有条理。
前面我们提到src/main.rs和src/lib.rs称为箱crate根。之所以有这样的名字,是因为这两个文件中的任何一个文件的内容在crate的模块结构的根上形成了一个名为crate的模块,称为模块树。
下面显示了上例中结构的模块树。
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
这个树显示了一些模块如何嵌套在另一个模块中(例如,在front_of_house中有hosting嵌套)。该树还显示了一些模块是彼此的兄弟,这意味着它们是在同一个模块中定义的(hosting和serving是在front_of_house中定义的)。继续家族隐喻,如果模块A包含在模块B中,我们说模块A是模块B的子模块,模块B是模块A的父模块。注意,整个模块树的根在名为crate的隐式模块下。
模块树可能会提醒您计算机上文件系统的目录树;这是一个非常贴切的比喻!就像文件系统中的目录一样,您使用模块来组织您的代码。就像目录中的文件一样,我们需要一种方法来找到我们的模块。
本章重点
- 模块的概念
- 模块的声明
- mod关键字
- use关键字
- pub关键字
- 模块树