一、核心概念铺垫
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)
// 1. 定义私有模块 greet(默认私有,仅当前文件可访问)modgreet{// 2. 用 pub 暴露函数(外部可访问)pubfnsay_hello(name:&str){println!("Hello, {}!",name);}// 私有函数(仅 greet 模块内部可访问,外部无法调用)fnsay_goodbye(name:&str){println!("Goodbye, {}!",name);}// 模块内部可调用私有函数pubfngreet_and_leave(name:&str){self::say_hello(name);// self:: 表示当前模块(相对路径)self::say_goodbye(name);}}// 3. 定义数学模块,暴露函数和常量modmath{pubconstPI:f64=3.1415926;pubfnadd(a:i32,b:i32)->i32{a+b}pubfnmultiply(a:i32,b:i32)->i32{a*b}}fnmain(){// 方式1:直接通过 模块名::项 访问(完整路径)greet::say_hello("Alice");greet::greet_and_leave("Bob");println!("10 + 20 = {}",math::add(10,20));println!("PI = {}",math::PI);// 方式2:用 use 引入项,简化后续调用usegreet::say_hello;usemath::{add,PI,multiply};// 批量引入多个项say_hello("Charlie");println!("30 + 40 = {}",add(30,40));println!("5 * 6 = {}",multiply(5,6));println!("PI value: {}",PI);// 方式3:用 use + * 通配符,引入模块下所有公有项(不推荐在工程中大量使用,易命名冲突)usemath::*;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
// 直接编写模块内容,无需再写 mod greet {}// 公有函数(外部可访问)pubfnsay_hello(name:&str){println!("Hello, {}!",name);}// 公有函数pubfngreet_and_leave(name:&str){// 调用当前模块的私有函数(相对路径,可省略 self::)say_goodbye(name);say_hello(name);}// 私有函数(仅 greet 模块内部可访问)fnsay_goodbye(name:&str){println!("Goodbye, {}!",name);}src/math.rs
// 公有常量pubconstPI:f64=3.1415926;// 公有函数pubfnadd(a:i32,b:i32)->i32{a+b}// 公有函数pubfnmultiply(a:i32,b:i32)->i32{a*b}// 私有函数(外部无法访问)fndivide(a:f64,b:f64)->f64{ifb==0.0{panic!("Division by zero!");}a/b}步骤2:入口文件声明并使用模块(main.rs)
// 1. 声明模块:Rust 会自动查找同名 .rs 文件(greet.rs → greet 模块)modgreet;modmath;fnmain(){// 方式1:完整路径访问greet::say_hello("Alice");println!("10 + 20 = {}",math::add(10,20));println!("PI = {}",math::PI);// 方式2:use 引入,简化调用usegreet::greet_and_leave;usemath::{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
// 公有加法函数pubfnadd(a:i32,b:i32)->i32{a+b}src/calculator/subtract.rs
// 公有减法函数pubfnsubtract(a:i32,b:i32)->i32{a-b}步骤2:编写模块入口文件(mod.rs)
// 统一声明 可 外访 子模块(关联 add.rs 和 subtract.rs)pubmodadd;pubmodsubtract;步骤3:入口文件使用模块(main.rs)
// 1. 声明子目录模块(关联 calculator/mod.rs)modcalculator;fnmain(){// 方式1:完整路径访问(因 mod.rs 导出了子模块,可直接访问)letsum=calculator::add::add(10,20);letdiff=calculator::subtract::subtract(50,15);println!("10 + 20 = {}",sum);println!("50 - 15 = {}",diff);// 方式2:use 引入,简化调用usecalculator::{add,subtract};letsum2=add::add(30,40);letdiff2=subtract::subtract(100,25);println!("30 + 40 = {}",sum2);println!("100 - 25 = {}",diff2);// 优化:直接引入子模块的函数(更简洁)usecalculator::add::addascalc_add;usecalculator::subtract::subtractascalc_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)
// 1. 嵌套声明模块:直接关联子目录下的文件modcalculator{// 声明并导出子模块(pub mod 让外部可访问)pubmodadd;pubmodsubtract;}fnmain(){// 使用方式与传统方式一致usecalculator::add::add;usecalculator::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::开头,从根模块(入口文件)开始查找// 在 main.rs 中usecrate::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::(父模块)开头,适用于模块内部// 在 calculator/mod.rs 中// self::add 表示当前模块(calculator)下的 add 子模块pubuseself::{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暴露;所有模块必须在入口文件或其父模块中声明