【译】关于Rust模块的清晰解释
原文链接: http://www.sheshbabu.com/posts/rust-module-system/
原文标题: Clear explanation of Rust’s module system
公众号: Rust碎碎念
翻译: Praying
Rust的模块(module)系统相当令人困惑,这也给很多初学者带来了挫败感。
在本文中,我将会通过实际的例子来解释模块系统以便于让你清晰地理解它是怎样工作的并且能够快速在自己的项目中应用。
由于Rust的模块系统比较独特,我希望读者以开放性思维来进行阅读,并且尽量不要将其与其他语言中的模块的工作方式进行比较。
让我们使用下面的文件结构来模拟一个真实世界中的项目:
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs
下面是一些使用我们的模块的不同方式:
下面的3个例子应该足以解释Rust的模块系统是如何工作的。
示例1
让我们从第一个例子开始 —— 在
main.rs中导入
config.rs。
// main.rs
fn main() {
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}
很多人常犯的第一个错误是因为我们有像
config.rs,
health_route.rs这样的文件,所以我们就认为这些文件就是
模块(module)且可以在其他的文件中将其导入。
下面是从我们的视角(文件系统树(file system tree))看到的内容和从编译器的角度(模块树(module tree))看到的内容:
令人惊奇,编译器只看到了
crate模块,也就是我们的
main.rs文件。这是因为我们需要显式地在Rust中构建模块树——在文件系统树和模块树之间不存在隐式的转换。
我们需要显式地在Rust中构建模块树——在文件系统树和模块树之间不存在隐式的转换。
想要把一个文件添加到模块树中,我们需要使用
mod关键字来将这个文件声明为一个子模块(submodule)。另一件使人们感到困惑的事情是你会认为在相同的文件里把一个文件声明为模块(译者注:比如使用mod关键字把config.rs声明为子模块,你可能认为需要在config.rs里来写声明)。但是我们需要在一个不同文件里进行声明!因为我们在这个模块树里只有
main.rs这个文件,所以要在
main.rs里将
config.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里面的一切默认都是私有(private)的,我们需要使用
pub关键字来让这个函数成为公开(public)的:
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");
}
正如我们之前所讨论的,我们只能对相同目录下的
my_module.rs或者
my_module/mod.rs使用
mod关键字。 所以为了能够在
main.rs中调用
routes/health_route.rs里定义的函数,我们需要做下面的事情:
- 创建一个名为
routes/mod.rs
的文件并且在main.rs
中声明routes
子模块 - 在
routes/mod.rs
中声明health_route
子模块并且使其成为公开(public)的 - 使
health_route.rs
里的函数公开(public)
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/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里调用
routes/user_route.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.rs里调用
print_user_route函数,而
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;
// routes/user_route.rs
- fn print_user_route() {
+ pub fn print_user_route() {
println!("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关键字指向父级作用域
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");
}
外部模块(External modules)
添加到
Cargo.toml里的依赖对于项目内的所有模块都是可以访问的。我们不需要显式地导入或声明任何东西来使用依赖项。
外部依赖对于项目内的所有模块都是可以访问的
例如,比如说我们在项目中添加了rand[1]这个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");
}
总结
- 模块系统是显式的(译者注:需要明确的声明)——不存在和文件系统的1:1映射
- 我们在一个文件的父级目录把它声明为模块,而不是在文件自身
mod
关键字用于声明子模块- 我们需要显式地将函数、结构体等声明为公开的,这样它们才可以被其他模块访问
pub
关键字把事物声明为公开的use
关键字用于简化(缩短)模块路径- 我们不需要显式声明第三方的模块
参考资料
[1]rand: https://crates.io/crates/rand
- 二分图最大匹配与最小顶点覆盖(教程系列)uva11419——我目前关于最大匹配最清晰的解释。
- 关于委托最清晰的解释
- 关于spark UI界面的解释,非常清晰
- 关于AVL(自平衡二叉排序树)的旋转名称的解释
- Java基础-关于session的详细解释
- 关于document.getElementById返回为null的解释
- 关于application/x-www-form-urlencoded等字符编码的解释说明
- objective-c中关于类型编码的解释
- 关于二级域名与三级域名的解释
- php关于引用的解释
- 记一次关于random模块的常用函数整合
- 关于“定金”的法律规定及司法解释
- 关于SIM800C MINI V4.0 V4版本 5v供电模块重启问题
- 关于np.all()d的解释
- 关于solr schema.xml 和solrconfig.xml的解释
- 关于使用Django admin模块插入中文时乱码的处理方法
- http-关于application/x-www-form-urlencoded等字符编码的解释说明
- 【开发工具】关于idea的多模块
- Python中关于模块的 '__name__' 变量的使用
- Hive中关于分区表的概念理解以及相关操作解释