一、核心概念铺垫
Rust 模块系统的三大核心关键字:
mod:定义模块 ,建立代码逻辑分组,同时关联对应的文件(Rust 会根据mod声明自动查找同名文件/目录)pub:控制可见性 ,Rust 模块默认私有(外部模块无法访问),需通过pub暴露模块、函数、结构体等项use:引入模块/项,简化路径书写,避免重复写完整模块路径- 模块与文件的映射规则:
- 入口文件:二进制工程(
src/main.rs)、库工程(src/lib.rs),所有模块必须在入口文件或其子孙模块中声明 - 同一目录:
mod xxx;对应xxx.rs文件 - 子目录:
mod xxx;传统对应xxx/mod.rs(Rust 2015),现代 Rust(2018+)可省略mod.rs,直接嵌套声明
- 入口文件:二进制工程(
二、单文件内的模块(基础演示)
先从单文件模块入手,理解 mod、pub、use 的基础用法。
文件结构
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.rs、calculator/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.rs 和 src/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 支持两种路径访问方式,优先推荐绝对路径(更稳定):
-
绝对路径 :以
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 // } -
相对路径 :以
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); // }
六、常见问题总结
- 找不到模块 :忘记在入口文件/父模块中用
mod 模块名;声明模块(Rust 无法自动发现文件) - 无法访问模块项 :未给模块/函数/结构体加
pub关键字(默认私有,外部不可访问) - 路径错误 :混淆绝对路径(
crate::)和相对路径(self::/super::),优先使用绝对路径 - 子目录模块无法访问 :传统方式未在
mod.rs中用pub use导出子模块;现代方式未用pub mod声明子模块
总结
- Rust 模块通过
mod定义、pub暴露、use引入,文件结构与模块结构一一对应 - 同一目录:
mod xxx;对应xxx.rs;子目录:传统对应xxx/mod.rs,现代可省略mod.rs - 访问方式:完整路径直接访问、
use引入简化访问,支持绝对路径(crate::)和相对路径(self::/super::) - 核心原则:模块默认私有,需显式用
pub暴露;所有模块必须在入口文件或其父模块中声明