起步
初看《Rust 程序设计语言》第七章的模块管理时,完全有不知所云之感。看完就“懵”住,也不晓得 mod
要怎么用。上 B 站找相关视频,稍解惑,却未细讲单个文件如何拆分到多个文件。最后在 github 找到 rust 语言编写的项目,方如梦初醒。
我很喜欢这种一致性设计,也就简单说一下。
mod 关键字
在 rust 中,可用 mod 关键字划分代码作用域,使代码结构清晰。
mod 的基本使用:
// 学校
mod school {
// 教师
mod teacher {
fn num() -> i32 { // 返回教师人数
5
}
}
// 学生
mod student {
fn num() -> i32 { // 返回学生人数
120
}
}
}
上述示例代码就是一个层次清晰的树状结构。mod [module_name] { ... }
会划出一个代码块,将一类你认为可以放在一起的项放在一起。
Rust 中的项:函数、方法、结构体、枚举、模块和常量。
要使用模块中的函数,可用::
符号作为路径分割符。比方你要获取学校中教师的数量,代码可以如下:
school::teacher::num();
事实上,你想在 main 函数中使用 school::teacher::num()
是会编译出错的,你会得到:“this module is private”。mod 会在控制作用域的同时,控制私有性。默认情况下,模块中的内容均为私有,对外不可见。pub
关键字可改变这种默认行为。
mod school {
// 对外可见
pub mod teacher {
pub fn num() -> i32 {
5
}
}
// 对外不可见
mod student {
fn num() -> i32 {
120
}
}
// classroom 模块对外可见
pub mod classroom {
// num 函数对外不可见
fn num() -> i32 {
6
}
}
}
fn main() {
// school::teacher::num() **可**正常使用
let teacher_nums = school::teacher::num();
println!("{}", teacher_nums);
// school::student::num() **不可**正常使用
// let teacher_nums = school::student::num();
}
mod 还可以把 B 模块引入到 A 模块中。如上述代码,teacher 模块直接写到 school 模块,teacher 理所当然就在 school 里。你也可以把子模块写到“世界各地”,再用 mod 把它们组合起来:
pub mod school {
pub mod teacher; // 或者 mod teacher;
pub mod student; // 或者 mod student;
}
pub mod teacher {
// ...
}
pub mod student {
// ...
}
crate、self、super、use
就像你看到的,school::teacher::num 很像一个路径(school\teacher\num),通过这种路径我们就能找到模块里的项。Rust 在这里的设计跟文件系统差不错,也有会绝对路径和相对路径的概念。
- 绝对路径:从 crate 根开始,以 crate 名 或者 字面值 crate 开头。
- 相对路径:从当前模块开始,以 self、super 或当前模块的标识符开头。
crate
先从最简单的 main.rs 文件开始:
pub mod school {
// 教师
pub mod teacher {
pub fn num() -> i32 { 5 }
pub fn average_students() -> i32 {
// 计算一个教师平均教授的学生数量
crate::school::student::num() / crate::school::teacher::num() // crate
}
}
// 学生
pub mod student {
pub fn num() -> i32 { 120 }
}
}
fn main() {
let avg = crate::school::teacher::average_students(); // crate
println!("{}", avg)
}
可以看到的是,不论是 num()
函数还是 average_students()
函数,我们都从 crate::
开始,也就是“根”开始,类似于文件系统中的 /
(/proc
, /etc
……)。作为二进制 crate,main.rs 文件就是它的根。
上述用法就是 “字面值 crate 开头” 的意思。
self
self
你可以理解为文件系统中的 .
,表示当前路径,如:cd ./project/src
,ls ./project/target
。所以上述代码可以用 self
把 crate
替换掉:
pub mod school {
// 教师
pub mod teacher {
pub fn num() -> i32 { 5 }
pub fn average_students() -> i32 {
// 计算一个教师平均教授的学生数量
crate::school::student::num() / self::num() // self
}
}
// 学生
pub mod student {
pub fn num() -> i32 { 120 }
}
}
fn main() {
let avg = self::school::teacher::average_students(); // self
println!("{}", avg)
}
- 说明:从“当前路径”无法访问到 student 模块中的 num() 函数,这里仍用 crate 的绝对路径表示。
正如文件系统中当前路径 .
可以省略,self
也能被省略。
pub mod school {
// 教师
pub mod teacher {
pub fn num() -> i32 { 5 }
pub fn average_students() -> i32 {
// 计算一个教师平均教授的学生数量
crate::school::student::num() / num() // self
}
}
// 学生
pub mod student {
pub fn num() -> i32 { 120 }
}
}
fn main() {
let avg = school::teacher::average_students(); // self
println!("{}", avg)
}
super
在做路径切换时,我们还会常用 cd ..
跳到父级目录,super
的作用就和 ..
类似。
代码:
crate::school::student::num() / num()
可以修改为:
super::student::num() / num()
这里的 super
表示 teacher 模块的父模块 school。
use
当我们不得不使用绝对路径,但绝对路径又太长时,可以用 use
关键字把名称引入作用域。
比方:
pub fn average_students() -> i32 {
crate::school::student::num() / self::num()
}
可修改为:
pub fn average_students() -> i32 {
use crate::school::student;
student::num() / num()
}
甚至还可以用 use ... as ...
对项重命名:
pub fn average_students() -> i32 {
use crate::school::student::num as student_num;
student_num() / num()
}
拆分到多文件
最开头我说“喜欢这种一致性设计”,是因为在 Rust 中,多文件中的代码引入方式与 mod 关键字基本类似。这很有意思,有效降低了使用者的学习成本。
mod school {
mod teacher {
// ...
}
mod student {
// ...
}
}
还拿之前的模块结构为例,现在把上述代码拆分到多个文件,我们很容易想到的方式是:
|—— src (dir)
| |—— main.rs (file)
| |—— school (dir)
| | |—— teacher.rs (file)
| | |—— student.rs (file)
// (school/teacher.rs)
// 返回教师的总数
pub fn num() -> i32 { 5 }
// 计算一个教师平均教授的学生数量
pub fn average_students() -> i32 {
use crate::school::student::num as student_num;
student_num() / num()
}
// (school/student.rs)
// 返回学生的总数
pub fn num() -> i32 { 120 }
这个时候你想在 main.rs 文件中调用 average_students() 函数就不行了。Rust 会将一个 xxx.rs 文件默认为一个模块,但是普通的文件夹不能被 Rust 编译器识别。当文件夹下有 mod.rs 文件时,该文件夹才能被识别为模块。
|—— src (dir)
| |—— main.rs (file)
| |—— school (dir)
| | |—— mod.rs (file) ** mod.rs 文件**
| | |—— teacher.rs (file)
| | |—— student.rs (file)
此时模块 teacher 、模块 student 与模块 school 还没有直接关系,需要在 mod.rs 文件中引入 teacher 和 student。
// (school/mod.rs)
pub mod teacher;
pub mod student;
现在 main.rs 文件就可以正常调用 average_students() 函数:
// (main.rs)
mod school; // 将 school 模块引入到 crate root 中
fn main() {
let avg = school::teacher::average_students();
println!("{}", avg)
}
正如你看到的,不论是单个文件还是在多个文件,模块的行为完全一致。
还不快抢沙发