rust基础之通过文件规划模块
本文中所有名词称呼都是我自己的习惯叫法,不保证准确,一切以官方为准。
在实际项目中,当模块变多或者变大时,需要将模块放入单独的文件中,让代码更好维护。当一个模块有许多子模块时,也可以通过文件夹的方式来组织这些子模块。
如果需要将文件夹作为一个模块,需要显示指定暴露哪些子模块。
rustc 1.30 版本之前只能使用在文件夹中创建 mod.rs 文件来暴露模块。
rustc 1.30 版本及之后也可以使用在文件夹同一目录创建文件夹同名的 .rs 文件的方式暴露模块。
对于暴露模块,我更愿意称其为引出,与引入对应。
module tree 模块树,rust语言用于管理工程中模块的抽象结构。想要使用模块,那么该模块必须包含在模块树中(rust-analyzer插件的提示:file not included in module tree)。
当模块引出后,其他模块可以将其引入,我把这一状态称作挂载。
mod.rs
├─src
│ │ file.rs
│ │ main.rs
│ │
│ └─dir0
│ │ file0.rs
│ │ mod.rs
│ │
│ └─dir1
│ file1.rs
│ mod.rs
创建如上目录结构。
// file.rs
pub fn file_fn() {
println!("file_fn");
}
// ####################################################
// file0.rs
pub mod mod0 {
pub fn fn0() {
println!("dir0_file0_mod0_fn0");
}
}
// dir0\mod.rs
pub mod dir1;
pub mod file0;
// ####################################################
// file1.rs
pub mod mod1 {
pub fn fn1() {
println!("dir1_file1_mod1_fn1");
}
}
// dir1\mod.rs
pub mod file1;
pub mod dir2;
main.rs
mod dir0;
mod file;
fn main() {
file::file_fn();
dir0::file0::mod0::fn0();
dir0::dir1::file1::mod1::fn1();
}
翻译成纯rust代码:
fn main() {
file::file_fn();
dir0::file0::mod0::fn0();
dir0::dir1::file1::mod1::fn1();
}
mod file {
pub fn file_fn() {
println!("file_fn");
}
}
mod dir0 {
pub mod file0 {
pub mod mod0 {
pub fn fn0() {
println!("dir0_file0_mod0_fn0");
}
}
}
pub mod dir1 {
pub mod file1 {
pub mod mod1 {
pub fn fn1() {
println!("dir1_file1_mod1_fn1");
}
}
}
}
}
rust编译器会将 .rs文件 和 包含mod.rs的文件夹 都当作 mod 来看待。
与 main.rs 同一目录下的 .rs文件 和 包含mod.rs的文件夹,可以直接由 main.rs 文件引入。这时模块就会被挂载到模块树上。
在其他目录下的 .rs文件 和 包含mod.rs的文件夹 可以在同级的 mod.rs 文件中引出,再由 main.rs 文件引入该目录就可以将模块挂载到模块树上。
更深层级的目录下的模块可以挂载到上一级模块,如此级级传导直到最外层被 main.rs 文件引入。
如此,就可以将 src 下的所有 .rs 文件都挂载到模块树上。
同名 .rs 文件
使用同名 .rs 文件和使用 mod.rs 文件几乎没有区别
├─src
│ │ dir0.rs
│ │ file.rs
│ │ main.rs
│ │
│ └─dir0
│ │ dir1.rs
│ │ file0.rs
│ │
│ └─dir1
│ file1.rs
如上,将 mod.rs 文件改成同名文件。不需要更改任何代码,只需要将 mod.rs 文件移动到文件夹同一目录下再将文件名改成和文件夹同名即可。
其他问题
调用 main.rs 文件中的函数
目录结构:
├─src
│ file.rs
│ main.rs
代码:
// main.rs
fn main() {
println!("hello world");
}
pub fn main_fn() {
println!("main_fn");
}
// file.rs
pub fn run_main_fn() {
crate::main_fn();
super::main_fn();
}
在 main.rs 中的函数可以使用模块路径来调用。并且由上代码可知,并不需要将 file.rs 挂载到模块树(不挂载到模块树只是对模块使用有影响,对于模块路径没有影响)。
main.rs 同一目录下互相调用
目录结构:
├─src
│ file.rs
│ file1.rs
│ main.rs
代码:
// main.rs
fn main() {
println!("hello world");
}
// file.rs
pub fn file_fn() {
println!("file_fn");
}
// file1.rs
pub fn run_file_fn() {
crate::file::file_fn();
super::file::file_fn();
}
调用 main.rs 文件目录下的其他文件函数同理,也是通过模块路径调用。
其他目录的相互调用
目录结构:
├─src
│ │ dir.rs
│ │ main.rs
│ │
│ └─dir
│ │ dir0.rs
│ │ dir1.rs
│ │
│ ├─dir0
│ │ file.rs
│ │
│ └─dir1
│ file.rs
代码:
// main.rs
fn main() {
println!("hello world");
}
// dir.rs
pub mod dir0;
pub mod dir1;
// dir0.rs
pub mod file;
// dir1.rs
pub mod file;
// dir0\file.rs
pub fn dir0_file_fn() {
println!("dir0_file_fn");
}
// dir1\file.rs
pub fn run_dir0_file_fn() {
crate::dir::dir0::file::dir0_file_fn();
super::super::dir0::file::dir0_file_fn();
}
其他目录文件相互调用,需要将模块引出,再通过模块路径调用。
总结
- 分文件规划代码和纯粹使用
mod来规划代码本质上是没有区别的。 - 要使用模块就必须将模块挂载到模块树上
src/main.rs和src/lib.rs被称为包根(crate root)。也就是模块树的根,模块最后都需要直接或者间接挂载到这里。- 模块树对于模块路径没有影响,只是对使用模块有影响
疑问
个人猜测,rust编译器不会编译不在模块树上的模块。因为我只看到过插件提示,运行时连警告都没有。期待大佬解惑。
待续~~~