rust基础之通过文件规划模块

fire_cat / 2023-07-25 / 原文

本文中所有名词称呼都是我自己的习惯叫法,不保证准确,一切以官方为准。

在实际项目中,当模块变多或者变大时,需要将模块放入单独的文件中,让代码更好维护。当一个模块有许多子模块时,也可以通过文件夹的方式来组织这些子模块。

如果需要将文件夹作为一个模块,需要显示指定暴露哪些子模块。

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.rssrc/lib.rs 被称为包根(crate root)。也就是模块树的根,模块最后都需要直接或者间接挂载到这里。
  • 模块树对于模块路径没有影响,只是对使用模块有影响

疑问

个人猜测,rust编译器不会编译不在模块树上的模块。因为我只看到过插件提示,运行时连警告都没有。期待大佬解惑。

待续~~~

参考自包和模块 - Rust语言圣经(Rust Course)