在了解 Cargo 之前,需要先熟悉Rust是如何组织代码的。

每个Rust程序都以root模块开头。如果你创建的是一个程序库文件,那么root的模块是lib.rs。如果你创建的是可执行文件,那么root的模块通常是main.rs。当你的代码越来越多时,Rust允许你将其拆分成模块。为了在组织项目时提供灵活性,有多种方法可以创建模块。


嵌套模块

创建模块最简单的方法是在现有模块中使用mod代码块,如下:

  1. mod food{
  2. struct Cake;
  3. struct Smoothie;
  4. struct Pizza;
  5. }
  6. fn main(){
  7. let eatable = Cake;
  8. }
  1. 上述代码创建了一个名为`food`的内部模块。要在现有模块中创建模块,我们需要使用关键字`mod`,后跟模块名称`food`,之后是一对花括号。在花括号内部,我们可以声明任何类型的元素,甚至嵌套模块。

在我们的food模块中,我们声明了三种结构:CakeSmoothiePizza。在main函数中,我们使用路径语法food::Cakefood模块创建一个Cake实例。接下来我们对该程序进行编译:

模块 - 图1

奇怪的是,编译器提示并未发现任何Cake类型定义。让我们根据编译器的提示将use food::Cake添加到代码中:

  1. mod food{
  2. struct Cake;
  3. struct Smoothie;
  4. struct Pizza;
  5. }
  6. use food::Cake;
  7. fn main(){
  8. let eatable = Cake;
  9. }
  1. 我们已经将`use food::Cake`添加到代码中。要使用模块中的任何元素,必须添加一个`use`声明。让我们再试一次:

模块 - 图2

我们得到另一个错误,提示说Cake是私有的。这为我们提供了关于模块的另一个重要特性,即私密性。默认情况下,模块内的元素是私有的。要使用模块中的任何元素,我们需要将元素纳入作用域。

这需要2个步骤:

  • 首先:我们需要通过使用关键字pub作为元素的前缀使元素变为公有的。
  • 其次:要使用该元素,我们需要添加一个use语句,就像之前使用food::Cake一样。

关键字use之后的内容是模块中的元素路径。使用路径语法指定模块中任何元素的路径,其语法是在元素名称之间使用双冒号(::)。路径语法通常以导入元素的模块名称开头,但它也可用于导入某些类型的单个字段,例如枚举。

让我们将Cake设定为公有的:

  1. mod food{
  2. pub struct Cake;
  3. struct Smoothie;
  4. struct Pizza;
  5. }
  6. use food::Cake;
  7. fn main(){
  8. let eatable = Cake;
  9. }
  1. `Cake`结构体之前添加了关键字`pub`,并通过`use food::Cake``root`模块中引用它。通过这些修改,我们的代码就能够成功编译。

将文件用作模块

模块也可以创建成文件,例如,对文件名为foo的目录下的文件—main.rs,我们可以在foo/bar.rs文件中创建一个名为bar的模块。然后在main.rs中,我们需要向编译器告知该模块,即使用mod foo声明模块。使用基于文件的模块时,这是额外的步骤。

为了掩饰将文件用作模块,我们创建了一个名为modules_demo的目录,它具有以下结构:

:::info

  • modules_demo

└── foo.rs

└── main.rs

:::

foo.rs中包含一个结构体Bar,以及它的impl代码块:

  1. pub struct Bar;
  2. impl Bar {
  3. pub fn init() {
  4. println!("Bar type initialized");
  5. }
  6. }

main.rs中使用这个模块,需在main.rs中添加如下代码:

  1. mod foo;
  2. use crate::foo::Bar;
  3. fn main(){
  4. let _bar = Bar::init();
  5. }
  1. 上述代码使用`mod foo`声明了模块foo,然后使用`use crate::foo:Bar`从模块调用结构体`Bar`。注意`use crate::foo::Bar`中的前缀`crate`,这里根据你使用的前缀定义,对应有3种方法可以导入模块中的元素。

绝对导入

  • crate:绝对导入前缀,指向当前项目的根目录。在上述代码中是root模块,即main.rs文件。任何在关键字crate之后的内容都会解析成来自root模块。

相对导入

  • self:相对导入前缀,指向与当前模块相关的元素。该前缀用于任何代码想要引用自身包含的模块时,例如use self::foo::Bar。这主要用于在父模块中重现导出子模块中的元素。
  • super:相对导入前缀,可以用于从父模块导入元素。诸如tests这类子模块将使用它从父模块导入元素。例如,如果模块bar希望访问父模块foo中的元素Foo,那么可以使用super::foo::Foo。将其导入模块bar

将目录用作模块

我们还可以创建一个目录来表示模块。这种方法允许我们将模块中的子模块作为文件和目录的层次结构。假设我们有一个目录my_program,它有一个名为foo的模块,并且对应的文件名为foo.rs。它包含一个名为Bar的类型和foo的函数。随着时间的推移,BarAPI数量不断增加,我们希望将它们作为子模块进行分离。可以使用基于目录的模块对此用例进行建模。

为了演示将目录用作模块,我们在名为my_program的目录中创建了一个程序。它在main.rs中有一个入口点,以及一个名为foo的目录。该目录现在包含一个名为bar.rs的子模块。

以下是my_program目录的结构:

:::info

  • my_program

└── foo/
└── bar.rs

└── foo.rs
└── main.rs

:::

  1. 为了让`Rust`识别`bar`,我们需要在目录`foo/`旁边创建一个名为`foo.rs`的兄弟文件。`foo.rs`文件将包含目录`foo/`中创建的任何子模块的模块声明(此处为 `bar.rs`

我们的bar.rs中包含以下内容:

  1. pub struct Bar;
  2. impl Bar {
  3. pub fn hello() {
  4. println!("Hello from Bar !");
  5. }
  6. }

我们有一个单位结构体bar,以及关联方法hello(),同时希望在main.rs中使用该API

:::info 注意:在较旧的 Rust 2015 中,子模块不要求 foo 文件夹和兄弟文件 foo.rs 一同出现, 而是使用 foo 中的 mod.rs 文件向编译器传达该目录是模块。Rust 2018 支持这两种方法。

:::

接下来,我们向foo.rs中添加一下代码:

  1. mod bar;
  2. pub use self::bar::Bar;
  3. pub fn do_foo() {
  4. println!("Hi from foo!");
  5. }
  1. 我们添加了模块`bar`的声明,然后从模块`bar`中重新导出`Bar`的元素。这要求将`Bar`定义为公有的(`pub`)。使用关键字`pub`的部分是从子模块重新导出的元素得以在父模块调用的原因。这里,我们使用关键字`self`来引用当前模块自身。在编写`use`语句时,重新导出一个简便的步骤,这有助于在导入隐藏在嵌套子模块中的元素时消除混乱。

self是相对导入的关键字。虽然鼓励使用crate进行绝对导入,但是在从父模块中的子模块重新导出元素时,使用self的表述更清晰。

最后,main.rs将能够使用这两个模块:

  1. mod foo;
  2. use foo::Bar;
  3. fn main(){
  4. foo::do_foo();
  5. Bar::hello();
  6. }
  1. 我们的`main.rs`声明了`foo`,然后导入结构体`Bar`,接下来我们就可以从`foo`调用`do_foo`方法,并在`Bar`上调用`hello`

和模块有关的内容不止目前介绍的这些。