Rust 模块管理与文件联动

一、核心概念铺垫

Rust 模块系统的三大核心关键字:

  1. mod定义模块 ,建立代码逻辑分组,同时关联对应的文件(Rust 会根据 mod 声明自动查找同名文件/目录)
  2. pub控制可见性 ,Rust 模块默认私有(外部模块无法访问),需通过 pub 暴露模块、函数、结构体等项
  3. use引入模块/项,简化路径书写,避免重复写完整模块路径
  4. 模块与文件的映射规则:
    • 入口文件:二进制工程(src/main.rs)、库工程(src/lib.rs),所有模块必须在入口文件或其子孙模块中声明
    • 同一目录:mod xxx; 对应 xxx.rs 文件
    • 子目录:mod xxx; 传统对应 xxx/mod.rs(Rust 2015),现代 Rust(2018+)可省略 mod.rs,直接嵌套声明

二、单文件内的模块(基础演示)

先从单文件模块入手,理解 modpubuse 的基础用法。

文件结构

复制代码
src/
└── main.rs  # 唯一文件

代码实现(src/main.rs)

rust 复制代码
// 1. 定义私有模块 greet(默认私有,仅当前文件可访问)
mod greet {
    // 2. 用 pub 暴露函数(外部可访问)
    pub fn say_hello(name: &str) {
        println!("Hello, {}!", name);
    }

    // 私有函数(仅 greet 模块内部可访问,外部无法调用)
    fn say_goodbye(name: &str) {
        println!("Goodbye, {}!", name);
    }

    // 模块内部可调用私有函数
    pub fn greet_and_leave(name: &str) {
        self::say_hello(name); // self:: 表示当前模块(相对路径)
        self::say_goodbye(name);
    }
}

// 3. 定义数学模块,暴露函数和常量
mod math {
    pub const PI: f64 = 3.1415926;

    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn multiply(a: i32, b: i32) -> i32 {
        a * b
    }
}

fn main() {
    // 方式1:直接通过 模块名::项 访问(完整路径)
    greet::say_hello("Alice");
    greet::greet_and_leave("Bob");
    println!("10 + 20 = {}", math::add(10, 20));
    println!("PI = {}", math::PI);

    // 方式2:用 use 引入项,简化后续调用
    use greet::say_hello;
    use math::{add, PI, multiply}; // 批量引入多个项

    say_hello("Charlie");
    println!("30 + 40 = {}", add(30, 40));
    println!("5 * 6 = {}", multiply(5, 6));
    println!("PI value: {}", PI);

    // 方式3:用 use + * 通配符,引入模块下所有公有项(不推荐在工程中大量使用,易命名冲突)
    use math::*;
    println!("7 * 8 = {}", multiply(7, 8));
}

运行结果

复制代码
Hello, Alice!
Hello, Bob!
Goodbye, Bob!
10 + 20 = 30
PI = 3.1415926
Hello, Charlie!
30 + 40 = 70
5 * 6 = 30
PI value: 3.1415926
7 * 8 = 56

三、同一目录下的多文件模块(核心场景)

当代码量增大时,需要将模块拆分到独立文件中,这是最常用的场景。

文件结构

复制代码
src/
├── main.rs       # 入口文件
├── greet.rs      # greet 模块对应的文件
└── math.rs       # math 模块对应的文件

步骤1:编写模块文件(greet.rs & math.rs

src/greet.rs
rust 复制代码
// 直接编写模块内容,无需再写 mod greet {}
// 公有函数(外部可访问)
pub fn say_hello(name: &str) {
    println!("Hello, {}!", name);
}

// 公有函数
pub fn greet_and_leave(name: &str) {
    // 调用当前模块的私有函数(相对路径,可省略 self::)
    say_goodbye(name);
    say_hello(name);
}

// 私有函数(仅 greet 模块内部可访问)
fn say_goodbye(name: &str) {
    println!("Goodbye, {}!", name);
}
src/math.rs
rust 复制代码
// 公有常量
pub const PI: f64 = 3.1415926;

// 公有函数
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 公有函数
pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

// 私有函数(外部无法访问)
fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        panic!("Division by zero!");
    }
    a / b
}

步骤2:入口文件声明并使用模块(main.rs

rust 复制代码
// 1. 声明模块:Rust 会自动查找同名 .rs 文件(greet.rs → greet 模块)
mod greet;
mod math;

fn main() {
    // 方式1:完整路径访问
    greet::say_hello("Alice");
    println!("10 + 20 = {}", math::add(10, 20));
    println!("PI = {}", math::PI);

    // 方式2:use 引入,简化调用
    use greet::greet_and_leave;
    use math::{add, multiply, PI};

    greet_and_leave("Bob");
    println!("3 * 4 = {}", multiply(3, 4));
    println!("50 + 60 = {}", add(50, 60));
    println!("Circle area (r=2): {}", PI * 2.0 * 2.0);

    // 错误示例:无法访问私有函数(math::divide 是私有,编译报错)
    // let _ = math::divide(10.0, 2.0);
}

运行结果

复制代码
Hello, Alice!
10 + 20 = 30
PI = 3.1415926
Goodbye, Bob!
Hello, Bob!
3 * 4 = 12
50 + 60 = 110
Circle area (r=2): 12.5663704

四、子目录下的模块(复杂工程场景)

对于大型工程,需要用子目录组织模块(如 calculator/add.rscalculator/subtract.rs),分两种实现方式(推荐现代简化方式)。

场景A:传统方式(兼容 Rust 2015,使用 mod.rs

文件结构
复制代码
src/
├── main.rs           # 入口文件
└── calculator/       # 子目录(对应 calculator 模块)
    ├── mod.rs        # calculator 模块的入口文件(必须)
    ├── add.rs        # calculator 的子模块 add
    └── subtract.rs   # calculator 的子模块 subtract
步骤1:编写子模块文件(add.rs & subtract.rs
src/calculator/add.rs
rust 复制代码
// 公有加法函数
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
src/calculator/subtract.rs
rust 复制代码
// 公有减法函数
pub fn subtract(a: i32, b: i32) -> i32 {
    a - b
}
步骤2:编写模块入口文件(mod.rs
rust 复制代码
// 统一声明 可 外访 子模块(关联 add.rs 和 subtract.rs)
pub mod add;
pub mod subtract;
步骤3:入口文件使用模块(main.rs
rust 复制代码
// 1. 声明子目录模块(关联 calculator/mod.rs)
mod calculator;

fn main() {
    // 方式1:完整路径访问(因 mod.rs 导出了子模块,可直接访问)
    let sum = calculator::add::add(10, 20);
    let diff = calculator::subtract::subtract(50, 15);
    println!("10 + 20 = {}", sum);
    println!("50 - 15 = {}", diff);

    // 方式2:use 引入,简化调用
    use calculator::{add, subtract};
    let sum2 = add::add(30, 40);
    let diff2 = subtract::subtract(100, 25);
    println!("30 + 40 = {}", sum2);
    println!("100 - 25 = {}", diff2);

    // 优化:直接引入子模块的函数(更简洁)
    use calculator::add::add as calc_add;
    use calculator::subtract::subtract as calc_sub;
    println!("5 + 5 = {}", calc_add(5, 5));
    println!("20 - 7 = {}", calc_sub(20, 7));
}

场景B:现代简化方式(Rust 2018+,无需 mod.rs

无需创建 mod.rs,直接在入口文件嵌套声明模块,更简洁。

文件结构
复制代码
src/
├── main.rs           # 入口文件
└── calculator/       # 子目录
    ├── add.rs        # add 模块
    └── subtract.rs   # subtract 模块
步骤1:子模块文件不变(add.rs & subtract.rs

同场景A的 src/calculator/add.rssrc/calculator/subtract.rs

步骤2:入口文件直接声明嵌套模块(main.rs
rust 复制代码
// 1. 嵌套声明模块:直接关联子目录下的文件
mod calculator {
    // 声明并导出子模块(pub mod 让外部可访问)
    pub mod add;
    pub mod subtract;
}

fn main() {
    // 使用方式与传统方式一致
    use calculator::add::add;
    use calculator::subtract::subtract;

    println!("100 + 200 = {}", add(100, 200));
    println!("300 - 150 = {}", subtract(300, 150));
}

运行结果(两种场景一致)

复制代码
10 + 20 = 30
50 - 15 = 35
30 + 40 = 70
100 - 25 = 75
5 + 5 = 10
20 - 7 = 13
100 + 200 = 300
300 - 150 = 150

五、补充:模块路径(绝对路径 vs 相对路径)

Rust 支持两种路径访问方式,优先推荐绝对路径(更稳定):

  1. 绝对路径 :以 crate:: 开头,从根模块(入口文件)开始查找

    rust 复制代码
    // 在 main.rs 中
    use crate::calculator::add::add;
    
    // 在 calculator/add.rs 中(访问 math 模块)
    // pub fn add_and_pi(a: i32, b: i32) -> f64 {
    //     (a + b) as f64 * crate::math::PI
    // }
  2. 相对路径 :以 self::(当前模块)、super::(父模块)开头,适用于模块内部

    rust 复制代码
    // 在 calculator/mod.rs 中
    // self::add 表示当前模块(calculator)下的 add 子模块
    pub use self::{add, subtract};
    
    // 在 greet.rs 中(若需访问 math 模块,用 super:: 回到父模块(根模块))
    // pub fn greet_with_pi(name: &str) {
    //     println!("Hello {}, PI is {}", name, super::math::PI);
    // }

六、常见问题总结

  1. 找不到模块 :忘记在入口文件/父模块中用 mod 模块名; 声明模块(Rust 无法自动发现文件)
  2. 无法访问模块项 :未给模块/函数/结构体加 pub 关键字(默认私有,外部不可访问)
  3. 路径错误 :混淆绝对路径(crate::)和相对路径(self::/super::),优先使用绝对路径
  4. 子目录模块无法访问 :传统方式未在 mod.rs 中用 pub use 导出子模块;现代方式未用 pub mod 声明子模块

总结

  1. Rust 模块通过 mod 定义、pub 暴露、use 引入,文件结构与模块结构一一对应
  2. 同一目录:mod xxx; 对应 xxx.rs;子目录:传统对应 xxx/mod.rs,现代可省略 mod.rs
  3. 访问方式:完整路径直接访问、use 引入简化访问,支持绝对路径(crate::)和相对路径(self::/super::
  4. 核心原则:模块默认私有,需显式用 pub 暴露;所有模块必须在入口文件或其父模块中声明
相关推荐
会算数的⑨2 小时前
Java场景化面经分享(一)—— JVM有关
java·开发语言·jvm·后端·面试
lpfasd1232 小时前
Spring Boot 4.0 新特性全解析 + 实操指南
java·spring boot·后端
葵花楹2 小时前
【JAVA期末复习】
java·开发语言·排序算法
一叶之秋14122 小时前
QT常用控件(二)
开发语言·qt
nuowenyadelunwen2 小时前
Harvard CS50 Week 6 Python
开发语言·python
饼干,2 小时前
期末考试3
开发语言·人工智能·python
山山而川 潺潺如镜2 小时前
python防止程序多开,但程序运行脚本
android·开发语言·python
神色自若2 小时前
Net8/Net10开源企业级跨平台数据采集系统,基于Avaloniaui
开发语言·avaloniaui·net8
莫生灬灬2 小时前
VueMultiBrowser - 开源多浏览器管理器
运维·开发语言·chrome·c#·自动化·vue