Rust 模块系统非常让人困惑,并且使新手有非常大的失败感.
在本文中,我会用实际例子来解释模块系统,你会清楚明白它是怎样工作的,并且可以立即应用到你们的项目中.
由于Rust模块系统十分独特,我请大家读本文时需要打开思维,不要往其它语言上套.
用以下结构来模拟真实项目:
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs
有不同的方式来让我们使用module
These 3 examples should be sufficient to explain how Rust’s module system works.
3个例子应该足以解决Rust模块系统是怎样工作的
例1
// main.rs
fn main() {
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}
第一个错误是,由于有些文件 config.rs, health_route.rs等,我们认为这些文件是modules
,我们可以导入他们到其它文件中.
下边是我位看到的文件系统树和编译器看到的模块树
非常意外,编译器只能看到crate模块,也以是main.rs文件,这是因为我们需要在Rust中显示构建模块树 - 文件系统树和模块树并没有隐式映射.
我们需要显示构建Rust模块树,没有隐式映射到文件系统功能
为了把文件添加到模块树中,需要声明使用mod关键字声明文件为子模块.下面的还可能使人困惑,你应该假设我们声明文件为一个模块,在同一个文件中.但是我们声明它在不同文件.由于我们只有main.rs
在模块树中,让我们声明config.rs作为main.rs的子模块.
mod 关键字声明子模块
关键字mod的语法是
mod my_module
下边的编译器会在相同的目录中寻找my_module.rs或my_module/mod.rs
my_project
├── Cargo.toml
└─┬ src
├── main.rs
└── my_module.rs
或
my_project
├── Cargo.toml
└─┬ src
├── main.rs
└─┬ my_module
└── mod.rs
因为main.rs和config.rs是在相同的目录,让我们声明config为一个模块吧
// main.rs
+ mod config;
fn main() {
+ config::print_config();
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}
我们访问函数print_config用::
语法
现在模块树是这样了
我们成功的声明了config模块!但是还不能调用config.rs中print_config(). 几乎在Rust中所有东西默认都是私有的,我们需要用关键字pub使函数为公有.
关键字pub可以使访问变为公有.
// main.rs
mod config;
fn main() {
config::print_config();
println!("main");
}
// config.rs
- fn print_config() {
+ pub fn print_config() {
println!("config");
}
现在,成功了,我位成功的在不同文件中调用定义的函数了.
例2
我们来从main.rs中调用routes/health_route.rs中的print_health_route().
// main.rs
mod config;
fn main() {
config::print_config();
println!("main");
}
// routes/health_route.rs
fn print_health_route() {
println!("health_route");
}
就像前边所说的,使用关键字mod仅仅是my_module.rs 或 my_module/mod.rs在同个目录才行.
所以为了从main.rs中调用routes/health_route.rs里边的函数,我们需要做以下事:
- 创建名为
routes/mod.rs
文件,并且在main.rs中定义routes
子模块. - 在
routes/mod.rs
中定义子模块health_route
,并且使它的访问权限为公开的. - 使health_route.rs中访问权限为公开的.
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
+ │ ├── mod.rs
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs
// main.rs
mod config;
+ mod routes;
fn main() {
+ routes::health_route::print_health_route();
config::print_config();
println!("main");
}
// routes/mod.rs
+ pub mod health_route;// routes/mod.rs
+ pub mod health_route;
// routes/health_route.rs
- fn print_health_route() {
+ pub fn print_health_route() {
println!("health_route");
}
下边是模块树的样子
现在可以调用文件夹内文件中的函数了.
例3
让我们尝试调用从main.rs => routes/user_route.rs => models/user_model.rs
// main.rs
mod config;
mod routes;
fn main() {
routes::health_route::print_health_route();
config::print_config();
println!("main");
}
// routes/user_route.rs
fn print_user_route() {
println!("user_route");
}
// models/user_model.rs
fn print_user_model() {
println!("user_model");
}
我们想要从main
中调用print_user_route
再调用print_user_model
中函数.
我们把之前的再做一遍 - 定义子模块,修改函数访问权限为共有,再添加mod.rs
文件
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── mod.rs
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
+ ├── mod.rs
└── user_model.rs
// main.rs
mod config;
mod routes;
+ mod models;
fn main() {
routes::health_route::print_health_route();
+ routes::user_route::print_user_route();
config::print_config();
println!("main");
}
// routes/mod.rs
pub mod health_route;
+ pub mod user_route;
// models/mod.rs
+ pub mod user_model;
// models/user_model.rs
- fn print_user_model() {
+ pub fn print_user_model() {
println!("user_model");
}
模块树现在长成这样
等等,我们实际还不能在print_user_route
中调用print_user_model
!到目前为止,我们只是从main.rs中调用其它模块中定义好的函数,在其它文件中我们该怎么做?
如果我位看看模块树, 函数print_user_model 在crate::models::user_model路径下.所以为了使用在文件中的模块,不是说main.rs.我们应该想想依据必要的模块树中的模块路径来访问.
// routes/user_route.rs
pub fn print_user_route() {
+ crate::models::user_model::print_user_model();
println!("user_route");
}
我们成功在非main.rs文件中调用到其它文件中定义的函数了.
super
如果我们组织文件有多层目录,引用完整的名字太长了. 假如某个原因,我们想从print_user_route
中调用print_health_route
.会分别有下边两个路径crate::routes::health_route
和 crate::routes::user_route
.
我们调用它通过使用完全的引用名crate::routes::health_route::print_health_route()
,但是我们也可以使用相对路径super::health_route::print_health_route()
.注意用super
来指向父作用域.
模块路径的关键字super指定父作用范围.
pub fn print_user_route() {
crate::routes::health_route::print_health_route();
// can also be called using
super::health_route::print_health_route();
println!("user_route");
}
use
在上边例子中使用完整引用名甚至相对引用名是冗长的.为了使引用名更短些,我们使用关键字use
来绑定新模块名字或重命名.
关键字use被用来缩短模块路径
pub fn print_user_route() {
crate::models::user_model::print_user_model();
println!("user_route");
}
上边代码可以重构为
use crate::models::user_model::print_user_model;
pub fn print_user_route() {
print_user_model();
println!("user_route");
}
替换使用print_user_model名字,可以重命名.
use crate::models::user_model::print_user_model as log_user_model;
pub fn print_user_route() {
log_user_model();
println!("user_route");
}
扩展模块
添加依赖到Cargo.toml
,在项目中所有模块都是可用的.不需要显示引入或声明任何其它的东西,以可以使用依赖.
在整个项目中的模块,扩展依赖是全局可用的
例如,在项目中添加rand crate, 我们可以在代码中直接使用
pub fn print_health_route() {
let random_number: u8 = rand::random();
println!("{}", random_number);
println!("health_route");
}
也可以使用use来缩短引用路径
use rand::random;
pub fn print_health_route() {
let random_number: u8 = random();
println!("{}", random_number);
println!("health_route");
}
总结
- 模块系统是显示引用的 - 并没有关系一对一和文件系统映射
- 声明文件作为模块在他父亲中,不是在他自己里边
- 关键字
mod
被用来声明子模块 - 需要显示声明函数,结构等访问权限为公用的,所有他们可以在其它模块里.
- 关键字
pub
使代码访问权限为共有 - 不需要显示声明第三方模块