在了解 Cargo 之前,需要先熟悉Rust是如何组织代码的。
每个Rust程序都以root模块开头。如果你创建的是一个程序库文件,那么root的模块是lib.rs。如果你创建的是可执行文件,那么root的模块通常是main.rs。当你的代码越来越多时,Rust允许你将其拆分成模块。为了在组织项目时提供灵活性,有多种方法可以创建模块。
嵌套模块
创建模块最简单的方法是在现有模块中使用mod代码块,如下:
mod food{struct Cake;struct Smoothie;struct Pizza;}fn main(){let eatable = Cake;}
上述代码创建了一个名为`food`的内部模块。要在现有模块中创建模块,我们需要使用关键字`mod`,后跟模块名称`food`,之后是一对花括号。在花括号内部,我们可以声明任何类型的元素,甚至嵌套模块。
在我们的food模块中,我们声明了三种结构:Cake、Smoothie和Pizza。在main函数中,我们使用路径语法food::Cake从food模块创建一个Cake实例。接下来我们对该程序进行编译:

奇怪的是,编译器提示并未发现任何Cake类型定义。让我们根据编译器的提示将use food::Cake添加到代码中:
mod food{struct Cake;struct Smoothie;struct Pizza;}use food::Cake;fn main(){let eatable = Cake;}
我们已经将`use food::Cake`添加到代码中。要使用模块中的任何元素,必须添加一个`use`声明。让我们再试一次:

我们得到另一个错误,提示说Cake是私有的。这为我们提供了关于模块的另一个重要特性,即私密性。默认情况下,模块内的元素是私有的。要使用模块中的任何元素,我们需要将元素纳入作用域。
这需要2个步骤:
- 首先:我们需要通过使用关键字
pub作为元素的前缀使元素变为公有的。 - 其次:要使用该元素,我们需要添加一个
use语句,就像之前使用food::Cake一样。
关键字use之后的内容是模块中的元素路径。使用路径语法指定模块中任何元素的路径,其语法是在元素名称之间使用双冒号(::)。路径语法通常以导入元素的模块名称开头,但它也可用于导入某些类型的单个字段,例如枚举。
让我们将Cake设定为公有的:
mod food{pub struct Cake;struct Smoothie;struct Pizza;}use food::Cake;fn main(){let eatable = Cake;}
在`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代码块:
pub struct Bar;impl Bar {pub fn init() {println!("Bar type initialized");}}
在main.rs中使用这个模块,需在main.rs中添加如下代码:
mod foo;use crate::foo::Bar;fn main(){let _bar = Bar::init();}
上述代码使用`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的函数。随着时间的推移,Bar的API数量不断增加,我们希望将它们作为子模块进行分离。可以使用基于目录的模块对此用例进行建模。
为了演示将目录用作模块,我们在名为my_program的目录中创建了一个程序。它在main.rs中有一个入口点,以及一个名为foo的目录。该目录现在包含一个名为bar.rs的子模块。
以下是my_program目录的结构:
:::info
- my_program
└── foo/
└── bar.rs
└── foo.rs
└── main.rs
:::
为了让`Rust`识别`bar`,我们需要在目录`foo/`旁边创建一个名为`foo.rs`的兄弟文件。`foo.rs`文件将包含目录`foo/`中创建的任何子模块的模块声明(此处为 `bar.rs`)
我们的bar.rs中包含以下内容:
pub struct Bar;impl Bar {pub fn hello() {println!("Hello from Bar !");}}
我们有一个单位结构体bar,以及关联方法hello(),同时希望在main.rs中使用该API。
:::info 注意:在较旧的 Rust 2015 中,子模块不要求 foo 文件夹和兄弟文件 foo.rs 一同出现, 而是使用 foo 中的 mod.rs 文件向编译器传达该目录是模块。Rust 2018 支持这两种方法。
:::
接下来,我们向foo.rs中添加一下代码:
mod bar;pub use self::bar::Bar;pub fn do_foo() {println!("Hi from foo!");}
我们添加了模块`bar`的声明,然后从模块`bar`中重新导出`Bar`的元素。这要求将`Bar`定义为公有的(`pub`)。使用关键字`pub`的部分是从子模块重新导出的元素得以在父模块调用的原因。这里,我们使用关键字`self`来引用当前模块自身。在编写`use`语句时,重新导出一个简便的步骤,这有助于在导入隐藏在嵌套子模块中的元素时消除混乱。
self是相对导入的关键字。虽然鼓励使用crate进行绝对导入,但是在从父模块中的子模块重新导出元素时,使用self的表述更清晰。
最后,main.rs将能够使用这两个模块:
mod foo;use foo::Bar;fn main(){foo::do_foo();Bar::hello();}
我们的`main.rs`声明了`foo`,然后导入结构体`Bar`,接下来我们就可以从`foo`调用`do_foo`方法,并在`Bar`上调用`hello`。
和模块有关的内容不止目前介绍的这些。
