模块系统 是《Rust 程序设计语言》第7章的全部内容,啰里八嗦了一整章,人都绕晕了,我相信大家和我一样,哈哈哈
- 什么是包(Packages)
简单来说,你的项目就是一个包,每一个包(Package)都有一个Cargo.toml
文件
1
2
|
用 cargo new xxx 创建出来的项目 xxx 就是一个二进制包
用 cargo new --lib xxx 创建出来的项目 xxx 就是一个类库包
|
- 什么是箱(Crate)
箱(Crate)类似一种"看不见,摸不着"
的概念,它是一个模块的树形结构,它形成了库或二进制项目
Crate 会将一个作用域内的相关功能分组到一起,使得该功能可以很方便地在多个项目之间共享。
1
2
3
4
5
6
7
8
9
10
11
|
编译生成的可执行文件,你就可以把它看成是一个箱(Crate),一个库(lib)项目,你也可以把他看成是一个箱(Crate)
一个包下至少有一个箱(Crate), 可以是类库箱(Crate),也可以是二进制箱(Crate)
一个包下可以包含任意多个二进制箱(Crate),但只能包含 0 或 1个类库箱(Crate)
Rust中的默认箱(crate):
src/main.rs // 二进制箱(binary crate)的根文件,该箱(crate)与包(package)同名
src/lib.rs // 类库箱(library crate)的根文件 ,该箱(crate)与包(package)同名
多个二进制箱(binary crates):
在src/bin目录下创建.rs文件, 每个文件都是一个二进制箱(Crate)
|
- 什么是模块(Modules)
模块就是实际能"看得见,摸得着"
的东西了,在Rust中用 mod
关键字来声明一个模块,我们来看一个简单的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 模块通过关键字mod加模块定义, 例如:
// 文件src/main.rs和src/lib.rs, 对应的箱是crate,
// 箱(crate)的模块结构(module structure), 也叫做模块树(module tree):
// crate // crate是一个关键字,它代表了当前Crate
// └── front_of_house
// ├── hosting
// ├── add_to_waitlist
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
|
你可能会问,我直接定义函数不行吗?和这个有什么区别?
答案是,直接定义函数没办法分组,比如上面例子的,我们可以用类似前缀 front_of_house::hosting::add_to_waitlist()
的方式来调用函数
模块
让我们可以将一个 crate
中的代码进行分组,以提高可读性与重用性。
模块还可以控制项的私有性
,即项是可以被外部代码使用的(public)
,还是作为一个内部实现的内容,不能被外部代码使用(private)
。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn run_it_works() {
// 引用模块的两种方式
// 绝对路径(absolute path)从 crate 根开始,以 crate 名或者字面值 crate 开头。
// 相对路径(relative path)从当前模块开始,以 self、super 或当前模块的标识符开头。
crate::front_of_house::hosting::add_to_waitlist(); // 绝对路径,由于front_of_house模块被定义在本Crate中,因此可以用crate关键字作为起始的绝对路径,当然也可以用当前Crate的名字作为起始路径
front_of_house::hosting::add_to_waitlist(); // 相对路径,模块定义在本Crate中,也可以省略crate关键字
}
|
1
2
3
4
5
6
7
8
9
10
11
|
mod front_of_house {
// pub 关键字使模块变为公有的,否则下面就引用不了
pub mod hosting {
pub fn add_to_waitlist() {} // pub 关键字使模块中的函数变为公有的,否则下面就引用不了
}
}
pub fn run_it_works() {
crate::front_of_house::hosting::add_to_waitlist();
front_of_house::hosting::add_to_waitlist();
}
|
1
2
3
4
5
6
7
8
9
|
fn serve_order() {}
mod back_of_house {
pub fn fix_incorrect_order() {
cook_order(); // 调用自己的私有函数是可以的,这个不用说也知道吧
super::serve_order(); // super 关键字可以从父目录开始调用函数
}
fn cook_order() {}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
pub mod serving {
pub fn take_order() {}
pub fn server_order() {}
pub fn take_payment() {}
}
}
use self::front_of_house::hosting::add_to_waitlist; // 相对路径引入
use crate::front_of_house::hosting; // 绝对路径引入
use crate::front_of_house::hosting as h; // 引入时,通过 as 关键字给它一个新的名字
// 使用 use 关键字,将某个名称导入当前作用域后,这个名称在此作用域中就可以使用了,但它对此作用域之外还是私有的。
// 如果想让其他人调用我们的代码时,也能够正常使用这个名称,就好像它本来就在当前作用域一样,那我们可以将 pub 和 use 合起来使用。
// 这种技术被称为 “重导出(re-exporting)”:我们不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域。
pub use crate::front_of_house::hosting as hi; // 重导出
use front_of_house::serving::*; // 如果希望将一个路径下所有公有项引入作用域,可以指定路径后跟 *,glob 运算符
use front_of_house::serving::{server_order, take_payment}; // 部分引入
pub fn eat_at_restaurant() {
// 引入后调用就比较简单了
hosting::add_to_waitlist();
add_to_waitlist();
h::add_to_waitlist();
hi::add_to_waitlist();
take_order();
server_order();
take_payment();
}
fn main() {
eat_at_restaurant();
}
|
例如,我们要将如下代码拆分到一个单独文件中
1
2
3
4
5
|
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
|
- 第一步我们需要创建一个和模块名同名的文件
src/front_of_house.rs
1
2
3
|
pub mod hosting {
pub fn add_to_waitlist() {}
}
|
- 在
src/lib.rs
中引入
1
2
3
4
5
6
7
|
mod front_of_house; // 在 mod front_of_house 后使用分号,而不是代码块,这将告诉 Rust 在另一个与模块同名的文件中加载模块的内容。
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
|
在书中还介绍了,你还可以再拆分,把 add_to_waitlist()
函数的实现也拆分到一个文件中,不过我认为那种方法很少用到
通常情况下我们把同类函数拆分到一个文件中就行了,没必要再多出一个目录,感兴趣的可以去看将模块拆分成多个文件章节
src/main.rs
和 src/lib.rs
有一个默认的和包同名的Crate
src/bin
目录下创建的 .rs
文件, 每个文件都是一个二进制箱(Crate)
- 你可在
当前 Crate
中使用 crate
关键字作为起始的绝对路径,来引用当前 Crate
中的模块
mod
关键字用来定义一个模块
pub
关键字用来导出一个模块