知识门户

Back

Rust 模块与文件系统:彻底搞懂 modpub 和文件结构#

很多 Rust 初学者都会被模块系统绕晕:mod.rs模块名.rspub moduse……到底怎么用?本文将带你从根源上理清 Rust 的模块与文件关系,并给出 2024 年最推荐的项目组织方式。


一、Rust 模块系统的核心哲学#

Rust 的模块系统与 Java、Go 等语言有一个根本不同:文件本身并不“属于”某个模块,而是由父模块通过 mod 关键字显式声明子模块的存在

你可以把 mod 想象成“告诉编译器这里有一个模块”,就像声明一个结构体或函数。编译器会根据声明的位置和文件系统的布局去加载对应的代码。

根模块(Crate Root)#

每个 crate 都有一个根模块:

  • 二进制 cratesrc/main.rs 是根
  • 库 cratesrc/lib.rs 是根

根模块是整个模块树的起点,所有其他模块都直接或间接挂在它下面。


二、创建模块的三种方式#

1. 内联模块(Inline Module)#

直接在文件中用花括号定义,适合小模块或仅内部使用的辅助代码:

// src/lib.rs
mod math {
    pub fn add(x: i32, y: i32) -> i32 {
        x + y
    }
    
    fn private_helper() {  // 默认私有
        println!("内部工具");
    }
}

pub fn use_math() {
    let result = math::add(2, 3);  // ✅ 公开函数可访问
    // math::private_helper();     // ❌ 私有函数无法访问
}
rust

模块内的项默认是私有的,需要用 pub 暴露出去。

2. 独立文件模块(Rust 2018 风格,推荐)#

将模块放在与模块同名的 .rs 文件中:

文件结构:

src/
├── lib.rs
└── network.rs          # network 模块
plaintext

声明(在 lib.rs 中):

// src/lib.rs
pub mod network;   // 编译器会查找 src/network.rs
rust

模块内容(在 network.rs 中):

// src/network.rs
pub fn connect() {
    println!("连接网络...");
}

// 还可以在此文件内继续定义子模块(内联)
pub mod tcp {
    pub fn send() {}
}
rust

3. 目录 + 入口文件(适用于子模块较多的场景)#

当模块需要有自己的子模块时,可以使用一个目录来组织:

方案 A(推荐):同名入口文件 模块名.rs + 同名子目录#

src/
├── lib.rs
├── front_of_house.rs          # 入口文件
└── front_of_house/            # 同名目录
    ├── hosting.rs
    └── serving.rs
plaintext
// src/lib.rs
pub mod front_of_house;   // 找 front_of_house.rs
rust
// src/front_of_house.rs (入口)
pub mod hosting;   // 找 front_of_house/hosting.rs
pub mod serving;   // 找 front_of_house/serving.rs
rust
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
rust
// src/front_of_house/serving.rs
pub fn take_order() {}
rust

方案 B(旧式,仍支持):mod.rs 作为入口#

src/
├── lib.rs
└── front_of_house/
    ├── mod.rs                # 入口文件
    ├── hosting.rs
    └── serving.rs
plaintext
// src/lib.rs
pub mod front_of_house;   // 若没有 front_of_house.rs,则找 front_of_house/mod.rs
rust
// src/front_of_house/mod.rs
pub mod hosting;
pub mod serving;
rust

注意:两种方案任选其一,不要同时使用同一模块的两种入口,否则会冲突。


三、编译器查找规则(决定性的规则)#

当你在某个模块中使用 mod foo; 时,编译器按以下优先级查找:

  1. 内联代码mod foo { ... } 中的花括号内容。
  2. 同名文件<当前模块所在目录>/foo.rs
  3. 旧式目录入口<当前模块所在目录>/foo/mod.rs

这个规则是递归的。例如,在 front_of_house.rs 中写 mod hosting; 时,编译器会查找:

  • front_of_house/hosting.rs
  • front_of_house/hosting/mod.rs

⚠️ 注意:如果父模块声明了 mod front_of_house;,但 front_of_house.rsfront_of_house/mod.rs 都不存在,编译器会报错(file not found for module)。仅仅有 front_of_house/ 目录是不够的,入口文件是必须的。


四、模块的可见性与路径#

路径访问#

使用绝对路径(从 crate 根开始)或相对路径(从当前模块开始):

// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();

// 相对路径
self::hosting::add_to_waitlist();  // 访问同级的子模块
super::some_function();            // 访问父模块中的函数
rust

使用 use 简化路径#

use 创建路径的快捷方式,类似其他语言的 import

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();   // 不再需要完整路径
}
rust

重命名与批量导入#

use crate::front_of_house::hosting as host;  // 别名

use std::{collections::HashMap, fmt::Result}; // 嵌套导入

use std::io::*;   // 导入所有公开项(谨慎使用)
rust

重新导出(pub use#

通过 pub use,可以把内部模块的项暴露给更外层,让使用者可以更简洁地访问:

// src/lib.rs
mod math {
    pub mod basic {
        pub fn add(x: i32, y: i32) -> i32 { x + y }
    }
}

pub use math::basic::add;   // 外部可以用 my_crate::add
rust

五、常见错误与排查#

错误现象常见原因解决方案
file not found for module 'foo'声明了 mod foo; 但缺少对应的 .rsmod.rs创建 foo.rsfoo/mod.rs
子模块无法访问忘记写 pub mod child;pub 函数添加 pub 关键字
在不同地方使用 mod 导致冲突同时存在 foo.rsfoo/mod.rs删除其中一个,统一风格
use 找不到路径路径写错或模块未正确声明检查模块树结构,使用 crate:: 绝对路径尝试

六、最佳实践总结(2024 推荐)#

  1. 首选方案:使用 模块名.rs 作为入口,同名目录存放子模块。避免使用 mod.rs,因为它在很多编辑器中会与其他语言的 mod 混淆,且不够直观。
  2. 模块声明显式化:每个子模块都在父模块中通过 mod 显式声明,这样模块树一目了然。
  3. 只使用一种风格:整个项目统一使用“同名文件+同名目录”方案,避免混用 mod.rs
  4. 合理使用 pub:只暴露必要的接口,保持封装性。
  5. 善用 pub use:为外部用户提供简洁的 API,隐藏内部复杂结构。

七、完整项目示例#

my_restaurant/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── front_of_house.rs
    ├── front_of_house/
    │   ├── hosting.rs
    │   └── serving.rs
    └── back_of_house.rs
plaintext

lib.rs:

pub mod front_of_house;
pub mod back_of_house;

pub use front_of_house::hosting;  // 重新导出,方便外部使用
rust

front_of_house.rs:

pub mod hosting;
pub mod serving;
rust

front_of_house/hosting.rs:

pub fn add_to_waitlist() {
    println!("已添加到等候列表");
}
rust

back_of_house.rs:

pub struct Breakfast {
    pub toast: String,
    seasonal_fruit: String,   // 私有字段
}

impl Breakfast {
    pub fn summer(toast: &str) -> Breakfast {
        Breakfast {
            toast: String::from(toast),
            seasonal_fruit: String::from("桃子"),
        }
    }
}
rust

外部使用:

use my_restaurant::{hosting, back_of_house::Breakfast};

fn main() {
    hosting::add_to_waitlist();
    let meal = Breakfast::summer("黑麦");
    println!("我要 {} 面包", meal.toast);
}
rust

八、总结#

Rust 的模块系统虽然初看复杂,但核心逻辑非常清晰:

  • 声明驱动:每个子模块必须由父模块使用 mod 显式声明。
  • 文件约定:编译器按固定规则查找 .rs 文件,理解这个规则是避免编译错误的关键。
  • 显式路径:通过 crate::super::use 来管理路径。

记住:文件不声明自己属于哪个模块,而是由父模块通过 mod 来告诉编译器它的存在。掌握这一点,你就掌握了 Rust 模块的精髓。


参考资料


Rust 模块与文件系统彻底指南:从原理到最佳实践
https://glinfei.space/blog/std-rust/mod
Author 甘霖飞
Published at 2026年8月27日
Comment seems to stuck. Try to refresh?✨