rust基础之分文件规划模块

发布时间 2023-07-24 23:35:01作者: fire_cat

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

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

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

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编译器不会编译不在模块树上的模块。因为我只看到过插件提示,运行时连警告都没有。期待大佬解惑。

待续~~~

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