前言
node.js
发布之初的突出特性之一就是使用起来非常简单,想要运行一个JavaScript
文件只需使用node file.js
命令即可,想要引入一个本地模块只需("./module.js")
即可。如果require()
不是相对或绝对路径,那么node
将在node_modules
文件夹中查找。曾经那是一段美好的时光,当然,如果你是近几年才接触node.js
的,那么您可能对它是否简单有不同的看法。不过在最初的时候,确实是很简单。
Rust
的模块系统和JavaScript
的相比有微妙的差异,当然掌握起来不困难,可在掌握之前,你经常会大呼意外
正文
如何引入文件?
最直接的回答是:你不必引入文件,在Rust
中你不需要import
文件。你只需要声明模块,当你声明了一个不含body
的模块时,Rust
会根据它的算法自动在文件系统中查找该模块,该算法和项目文件目录结构高度相关
用mod
关键字来声明模块,如果你写的是mod my_module;
,Rust
会自动搜索出名为my_module.rs
的文件,查询my_module
的模块
不存在node
中的那种相对文件路径的关系
参考:可以参考 将模块分割进不同文件
如何从其它模块引入函数
use
语句可以会将其它模块的内容置于当前作用于,以便可以直接使用,这个机制对外在的依赖以及本地模块都是一样的,如:
some_module::some_function();
// 等价于
use some_module::some_function;
some_function()
可以用{}
引入多个内容:
use std::io::{Read, Write};
用super
引用父级模块的内容:
fn work() {}
mod test {
use super::work;
}
用crate
引用crate
根的内容:
use crate::some_module::some_function
重要概念
crate 与 crate根
一个Rust
项目就是一个crate
,crate root
指的是Rust
编译器编译的起始源文件。在Node.js
中通常是index.js
,途径是通过package.json
中的"main"
进行配置。在Rust
中,独立的可执行项目的crate root
是main.rs
,代码库则的crate root
则是lib.rs
,这通过Cargo.toml
来配置
crate root
规定了Rust
搜寻引入模块的根路径
module
如果你一开始就知道Rust
模块与文件名或路径无关,那么后面就容易了。你可能会在文档和其它项目里看到不同的说法,不过这些说法也是假的,module
仅仅是Rust
内在的一个概念
一个module
是一个namespace
,在这里你将相似的代码归类到一块,module
控制了这些代码的可见性,例如public
或private
。一个文件中可以有很多的module
,module
之间也可以嵌套使用:
fn main() {
my_module::print(my_module::submodule::MSG);
}
mod my_module {
pub fn print(msg: &str) {
println!("{}", msg);
}
pub mod submodule {
pub const MSG: &str = "Hello world!";
}
}
人在划分代码结构的时候,更希望代码按照逻辑放到不同的文件中,Rust
支持在本地文件系统中自动查询模块
假设我们想将my_module
放到另外一个文件中,我们建立一个my_module.rs
文件,并将代码拷贝过去,然后在main.rs
中保留一个没有内容的mod my_module;
:
// src/main.rs
fn main() {
my_module::print(my_module::submodule::MSG);
}
mod my_module;
// src/my_module.rs
pub fn print(msg: &str) {
println!("{}", msg);
}
pub mod submodule {
pub const MSG: &str = "Hello world!";
}
main.js
声明了一个名叫my_module
的模块,但是它的内容是空的,Rust
就需要去寻找它的定义了
文件查询算法
- 从和当前模块的相同路径开始,例如你在
crate root
,则路径是./
。如果是在one::two::three
模块中,则起始就是./one/two/three
- 查询和导入模块同名的文件,例如
mod my_module
会自动查询my_module.rs
文件 - 会在导入模块名称的文件夹中查询
mod.rs
文件,如my_module/mod.rs
- 如果有0个匹配或多个匹配都是会使问题复杂
注意:
mod.rs
的形式是技术遗留问题,在一些项目里可能还会看到类似的用法,但已不被推荐
这就意味着一个组织良好的项目,它的模块就是文件系统相对路径的一个镜像。看下面这段代码:
// src/mian.rs
fn main() {}
mod module_a;
mod one {
mod two {
mod module_b;
}
}
mod module_a
和mod module_b
声明在同一个文件中,Rust
会去哪里查询呢?
会在./module_a.rs
或./module_a/mod.rs
中寻找module_a
会在./one/two/module_b.rs
或./one/two/module_b/mod.rs
中寻找module_b
可见性
默认情况下所有(trait
方法和枚举变体除外)都是private
,不过可见性只是起到模块壁垒的作用,同一个模块内可以随意访问
定义在上层的内容默认情况下看不到更深层的内容,除非用pub
来改变可见性。你可以在pub上使用修改器来调整可见性,例如:
pub(crate)
:对于同一个crate
是可见的pub(super)
:只对父模块可见
还有其他的修改器,但是不太常用,具体可以参见后面参考文章列出的相关内容
light示例的改造
之前的light
项目已经变的比较大了,是时候按照文件划分进行重构了,这是一个 重构示例
参考文章
总结
mod
不是import
,一旦克服了这个误解,你就算明白模块系统了,下一篇文章我们会继续接上之前String
未讲完的内容
更多
- 写给前端看的Rust教程(1)从nvm到rust
- 写给前端看的Rust教程(2)从npm到cargo
- 写给前端看的Rust教程(3)配置Visual Studio Code
- 写给前端看的Rust教程(4)Hello World
- 写给前端看的Rust教程(5)借用&所有权
- 写给前端看的Rust教程(6)String 第一部分
- 写给前端看的Rust教程(7)语言篇[上]
- 写给前端看的Rust教程(8)语言篇[中]
- 写给前端看的Rust教程(9)语言篇[下]
- 写给前端看的Rust教程(10)从 Mixins 到 Traits
- 写给前端看的Rust教程(11)Module
- 写给前端看的Rust教程(12)String 第二部分
- 写给前端看的Rust教程(13)Results & Options