在之前的文章中,我们都是在Rust Playground上面书写我们的代码例子。在这篇文章我们将简单的介绍rust模块系统,这里包括,package, crate,module和path。通过这一篇文章的学习,我们在编写较为复杂的项目时,合理地对代码进行组织与管理我们的项目代码。
Rust模块系统Rust提供了一系列的功能来帮助我们管理代码,这其中就包括决定哪些细节是暴露的,哪些细节是私有的,以及不同的作用域内存在哪些名称。这些功能有时被统称为模块系统,它们包括如下几个方面:
包是由一个或多个提供相关功能的单元包集合而成,它所附带的配置文件Cargo.toml描述了如何构建这些单元包的信息。而单元包可以被用于生成二进制程序或库。我们将Rust编译时所使用的入口文件称作这个单元包的根节点,它同时也是单元包的根模块。一个包中只能拥有最多一个库单元包。其次,包可以拥有任意多个二进制单元包。最后,包内必须存在至少一个单元包(库单元包或二进制单元包)。我们用一个例子来说明一下:
➜ cargo new hello
Created binary (application) `hello` package
我们使用cargo new命令生成了一个名为hello的二进制单元包,我们使用tree命令查看cargo给我们生成了那些文件,具体操作如下:
➜ tree
.
└── hello
├── Cargo.toml
└── src
└── main.rs
3 directories, 2 files
在不加任何参数的情况下cargo将会帮助我们生成一个二进制的crate。cargo会默认将src/main.rs视作一个二进制单元包的根节点而无须指定,这个二进制单元包与包拥有相同的名称。单元包可以将相关的功能分组,并放到同一作用域下,这样便可以使这些功能轻松地在多个项目中共享。
模块系统小实战我们将使用一个小小的实战来一步步说明Rust中的模块系统。
➜ cargo new school
Created binary (application) `school` package
2. 创建如下目录和文件
➜ school git:(master) ✗ tree src
src
├── info.rs
├── main.rs
├── student
│ ├── mod.rs
│ ├── play.rs
│ └── study.rs
└── teacher
├── mod.rs
└── teaching.rs
3 directories, 7 files
3. 键入如下代码
// info.rs
fn print_school_info() {
println!("985 and 211");
}
// main.rs
fn main() {
println!("Hello, world!");
}
现在我们需要在main.rs中调用info.rs中的print_school_info函数,我们该怎么做?有人说是直接将info.rs当作模块导入到main.rs当中,这个这想法很好,怎么做呢?由于Rust编译器只能看到crate模块,也就是main.rs,所以我们需要显示的在Rust中构建模块树,需要注意的是文件系统树和模块树之间不存在隐式的转换。具体做法如下所示:
// info.rs
fn print_school_info() {
println!("985 and 211");
}
// main.rs
mod info; // 使用mod关键字将info声明为子模块
fn main() {
println!("Hello, world!");
info::print_school_info(); // 这里我们使用::语法使用info下的print_school_info函数
}
我们想要把一个文件添加到模块树中,我们需要使用mod关键字来将这个文件声明为一个子模块,就像上面的做法一样。好了,我们现在运行代码
➜ school git:(master) ✗ cargo run
Compiling school v0.1.0 (/home/test/Workspace/RustCoder/zhihurust/school)
error[E0603]: function `print_school_info` is private
--> src/main.rs:5:11
|
5 | info::print_school_info();
| ^^^^^^^^^^^^^^^^^ private function
|
note: the function `print_school_info` is defined here
--> src/info.rs:1:1
|
1 | fn print_school_info() {
| ^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `school` due to previous error
为什么会出现这种情况?Rust编译器其实已经给出了答案,因为默认情况下模块里面的内容是私有的,我们只需在定义或者声明模块中的函数或常量时使用pub关键字表示函数或常量是公开的就可以了,具体做法如下
// info.rs
pub fn print_school_info() { // 在函数前面加入pub关键字
println!("985 and 211");
}
// main.rs
mod info;
fn main() {
println!("Hello, world!");
info::print_school_info();
}
现在再来运行修改后的代码,运行结果如下所示:
➜ school git:(master) ✗ cargo run
Compiling school v0.1.0 (/home/test/Workspace/RustCoder/zhihurust/school)
Finished dev [unoptimized debuginfo] target(s) in 0.09s
Running `target/debug/school`
Hello, world!
985 and 211
4. 怎么调用目录级别的模块
分别在对应的文件键入如下代码
// src/teacher/teaching.rs
pub fn training() {
println!("In training");
}
// 在src/teacher/目录创建mod.rs文件mod.ts的内容如下
pub mod teaching; // teaching对应文件名,将teaching声明为模块导出
// main.rs
mod info;
mod teacher; // 文件名称
fn main() {
println!("Hello, world!");
info::print_school_info();
teacher::teaching::training(); // 这里的调用意思记作teacher目录下的teaching文件下的training函数
}
teacher目录下的mod.rs文件相当于python中的__init__.py文件,我们运行我们的代码看是否正确,运行结果如下所示:
➜ school git:(master) ✗ cargo run
Compiling school v0.1.0 (/home/test/Workspace/RustCoder/zhihurust/school)
Finished dev [unoptimized debuginfo] target(s) in 0.10s
Running `target/debug/school`
Hello, world!
985 and 211
In training
5. 在子模块里调用其他子模块中的函数
分别在对应的文件里键入如下代码
// src/student/mod.rs
pub mod study;
// src/student/study.rs
pub fn do_home_work() {
println!("do home work");
}
// src/teacher/teaching.rs
pub fn training() {
println!("In training");
crate::student::study::do_home_work(); // 在teaching这个子模块中调用study.rs子模块的do_home_work函数
}
// src/main.rs
mod info;
mod teacher;
mod student; // 需要在主模块中声明student这个子模块,从而才能构建出模块树,不然编译运行不通过
fn main() {
println!("Hello, world!");
info::print_school_info();
teacher::teaching::training();
}
注意我们在src/teacher/teaching.rs使用了crate::student::study::do_home_work();这样的语法形式来调用student子模的函数,其中crate的意思是从根crate进行查找也就是main.rs所在的目录,其实就是以绝对路径的方式进行调用。好了我们运行一下代码,看看是否报错,运行结果如下所示:
➜ school git:(master) ✗ cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/school`
Hello, world!
985 and 211
In training
do home work
6. 使用super关键字调用模块中的函数
如果我们的文件组织包含多级目录,完整的限定名就会变得很长,这时候我们就可以使用super这个关键字来减少这中路径长的问题了。
分别在对应文件下,键入如下代码
// src/student/play.rs
pub fn play_football() {
println!("play football ...");
}
// src/student/mod.rs
pub mod study;
pub mod play;
// src/student/study.rs
pub fn do_home_work() {
println!("do home work");
println!("below using crate keyword");
crate::student::play::play_football(); // 使用crate关键字调用play子模块中play_football函数
println!("below using super keyword");
super::play::play_football(); // 使用super关键字调用play子模块中play_football函数
}
其实super就是相对路径,即从父模块开始构造相对路径。运行我们的代码看看是否正确吧,运行结果如下所示:
➜ school git:(master) ✗ cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/school`
Hello, world!
985 and 211
In training
do home work
below using crate keyword
play football ...
below using super keyword
play football ...
7. 使用use关键字
无论是使用crate完整的限定名还是使用super相对路径的限定名都很冗长。为了让限定名变得更短,我们可以使用use关键字来给路径绑定一个新名字或者别名。
分别在对应的文件里键入如下代码
// src/student/study.rs
pub fn do_home_work() {
println!("do home work");
println!("below using crate keyword");
crate::student::play::play_football();
println!("below using super keyword");
super::play::play_football();
}
pub fn bye() {
println!("goodbye!");
}
// src/teacher/teaching.rs
use crate::student::study::bye; // use相当于导入操作了
pub fn training() {
println!("In training");
crate::student::study::do_home_work();
bye(); // 直接使用bye函数
}
8. 使用外部模块
使用外部模块很简单,只需在Cargo.toml文件里添加对应的模块即可,具体操作如下:
// Cargo.toml文件
[package]
name = "school"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] // 在dependencies项下面中添加外部模块
rand = "0.5.5" // rand为外部模块名称, 等号后面为外部模块的版本号
// 保存Cargo.toml文件运行cargo run它就会从crates.io这各站点上下载外部模块了
➜ school git:(master) ✗ cargo run
Updating crates.io index
Downloaded rand v0.5.6
Downloaded libc v0.2.140
Downloaded 2 crates (806.4 KB) in 36.19s
Compiling libc v0.2.140
Compiling rand_core v0.4.2
Compiling rand_core v0.3.1
Compiling rand v0.5.6
Compiling school v0.1.0 (/home/test/Workspace/RustCoder/zhihurust/school)
Finished dev [unoptimized debuginfo] target(s) in 19m 46s
Running `target/debug/school`
Hello, world!
985 and 211
In training
do home work
below using crate keyword
play football ...
below using super keyword
play football ...
goodbye!
在对应的文件键入如下代码
// src/student/play.rs
use rand::Rng;
pub fn play_football() {
println!("play football ...");
let n = rand::thread_rng().gen_range(2, 11);
println!("the n is {}", n);
}
运行结果如下所示:
➜ school git:(master) ✗ cargo run
Compiling school v0.1.0 (/home/tesst/Workspace/RustCoder/zhihurust/school)
Finished dev [unoptimized debuginfo] target(s) in 0.13s
Running `target/debug/school`
Hello, world!
985 and 211
In training
do home work
below using crate keyword
play football ...
the n is 10
below using super keyword
play football ...
the n is 8
goodbye!
9. 使用通配符,pub use和as
关于使用通配符,pub use和as的用法我就不写代码演示了,我在这简单说一下吧,它们的写法和使用很简单
// 通配符用法
use std::collections::*; // 这跟python里面是类似的,例如from os import *
// pub use用法, 比如你在一个子模块里使用了pub use crate::student::play::do_home_work;
// 这就相当与你重新把这个模块导出出去了,我们不仅将此条目引入了作用域,而且使该条目可以被
// 外部代码从新的作用域引入自己的作用域
// as 的用法,就是相当于起别名,这一点跟python中的as也是类似的,例如 import os as sysos
小结
在Rust中允许我们将包拆分为不同的单元包,并将单元包拆分为不同的模块,从而使我们能够在其他模块中引用某个特定模块内定义的函数,方法或者常量等。为了引用外部模块,我们需要指定它们的绝对路径或相对路径。我们可以通过use语句将这些路径引入到其作用域中,接着在该作用域中使用较短的路径来多次使用对应的条目。模块中的代码是默认私有的,但我们可以通过添加pub关键字来将定义声明为公有的。
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved