Rust 学习笔记(14)-模块系统

模块系统 是《Rust 程序设计语言》第7章的全部内容,啰里八嗦了一整章,人都绕晕了,我相信大家和我一样,哈哈哈

  1. 什么是包(Packages)

简单来说,你的项目就是一个包,每一个包(Package)都有一个Cargo.toml文件

1
2
用 cargo new xxx 创建出来的项目 xxx 就是一个二进制包  
用 cargo new --lib xxx 创建出来的项目 xxx 就是一个类库包
  1. 什么是箱(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)
  1. 什么是模块(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();
}
  • super 关键字
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() {}
}
  • use 关键字将路径引入作用域
 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() {}
    }
}
  1. 第一步我们需要创建一个和模块名同名的文件 src/front_of_house.rs
1
2
3
pub mod hosting {
    pub fn add_to_waitlist() {}
}
  1. 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() 函数的实现也拆分到一个文件中,不过我认为那种方法很少用到
通常情况下我们把同类函数拆分到一个文件中就行了,没必要再多出一个目录,感兴趣的可以去看将模块拆分成多个文件章节

总结一下

  1. src/main.rssrc/lib.rs 有一个默认的和包同名的Crate
  2. src/bin 目录下创建的 .rs 文件, 每个文件都是一个二进制箱(Crate)
  3. 你可在当前 Crate 中使用 crate 关键字作为起始的绝对路径,来引用当前 Crate 中的模块
  4. mod 关键字用来定义一个模块
  5. pub 关键字用来导出一个模块
updatedupdated2025-03-012025-03-01