文章目录
- 一、回顾与导入
- 二、核心概念
- 什么是 Crate?
- 什么是 Package?
- 三、定义模块
- 3.1 使用 `mod` 关键字
- 3.2 模块的私有性
- 四、路径(Path)
- 使用 `super` 访问父模块
- 五、use 关键字
- 5.1 使用 `as` 重命名
- 5.2 导入多项
- 5.3 通配符 `*`
- 5.4 嵌套路径
- 六、拆分模块到多个文件
- 6.1 传统方式(Rust 2015 风格,仍可用)
- 6.2 现代方式(Rust 2018+,推荐)
- 6.3 另一种现代方式(不推荐 mod.rs)
- 七、pub use —— 重导出
- 八、Cargo.toml 与第三方依赖
- 8.1 添加依赖
- 8.2 使用第三方 crate
- 九、完整项目示例
- 项目结构
- Cargo.toml
- src/lib.rs
- src/front_of_house/mod.rs
- src/front_of_house/hosting.rs
- src/back_of_house.rs
- src/main.rs
- 十、常见陷阱与最佳实践
- 陷阱 1:忘记添加 `pub`
- 陷阱 2:路径混乱
- 最佳实践总结
- 十一、总结与速查
- 十二、思考题
- 参考链接
返回首页 | 上篇 | 下篇
掌握 Rust 的模块化组织 —— 包、Crate、模块、use 以及项目结构最佳实践
一、回顾与导入
前面十篇文章我们学习了 Rust 的基础语法和核心特性。随着项目规模增长,我们需要一种方式来组织代码,将功能拆分到不同的文件中,并控制哪些内容对外可见。
这就是 Rust 的模块系统的职责。
💡 Rust 的模块系统帮助你组织代码、控制私有性、管理依赖。
二、核心概念
Rust 的模块系统包含以下几个层级(从大到小):
| 概念 | 说明 | 示例 |
|---|---|---|
| 包(Package) | 一个或多个 Crate,有Cargo.toml | cargo new my_project |
| Crate | 一个编译单元,产生一个库或可执行文件 | lib.rs或main.rs |
| 模块(Module) | 控制作用域和私有性的代码组织单元 | mod network |
| 路径(Path) | 引用模块中的项 | crate::network::connect |
什么是 Crate?
Crate 是 Rust 的编译单元。rustc每次编译,处理的都是一个 Crate。Crate 可以生成:
- 二进制程序(有
main函数) - 库(可被其他项目使用,没有
main函数)
什么是 Package?
Package 包含:
- 一个
Cargo.toml文件 - 至少一个 Crate(可以是库、二进制或两者都有)
典型结构: my_package/ ├── Cargo.toml ├── src/ │ ├── main.rs # 二进制 crate 根 │ └── lib.rs # 库 crate 根(可选)三、定义模块
3.1 使用mod关键字
可以在一个文件中定义多个模块:
// src/main.rsmodfront_of_house{modhosting{fnadd_to_waitlist(){}fnseat_at_table(){}}modserving{fntake_order(){}fnserve_order(){}fntake_payment(){}}}3.2 模块的私有性
Rust 中所有项(函数、结构体、枚举等)默认是私有的。父模块不能访问子模块的私有项,但子模块可以访问父模块的项。
modfront_of_house{pubmodhosting{// 添加 pub 让模块对外可见pubfnadd_to_waitlist(){}// 添加 pub 让函数对外可见}}fnmain(){// 需要 pub 才能访问crate::front_of_house::hosting::add_to_waitlist();}四、路径(Path)
路径用于引用模块中的项,有两种形式:
| 类型 | 语法 | 说明 |
|---|---|---|
| 绝对路径 | crate::module::item | 从当前 crate 根开始 |
| 相对路径 | self::module::item或super::item | 从当前模块开始 |
modfront_of_house{pubmodhosting{pubfnadd_to_waitlist(){}}}fnmain(){// 绝对路径crate::front_of_house::hosting::add_to_waitlist();// 相对路径(从当前模块开始)front_of_house::hosting::add_to_waitlist();}使用super访问父模块
fnserve_order(){}modback_of_house{fnfix_incorrect_order(){cook_order();super::serve_order();// super 指向父模块}fncook_order(){}}五、use 关键字
use用于将路径导入作用域,简化重复引用。
modfront_of_house{pubmodhosting{pubfnadd_to_waitlist(){}}}usecrate::front_of_house::hosting;fnmain(){hosting::add_to_waitlist();// 不需要写完整路径}5.1 使用as重命名
usestd::fmt::Result;usestd::io::ResultasIoResult;// 解决名称冲突5.2 导入多项
usestd::collections::{HashMap,HashSet,VecDeque};// 等价于// use std::collections::HashMap;// use std::collections::HashSet;// use std::collections::VecDeque;5.3 通配符*
usestd::collections::*;// 导入所有公共项谨慎使用,可能造成命名冲突和可读性下降。
5.4 嵌套路径
usestd::{cmp::Ordering,collections::{HashMap,HashSet},io::{self,Write},};fnmain(){letmutmap=HashMap::new();io::stdout().write(b"hello").unwrap();}六、拆分模块到多个文件
随着项目变大,需要将模块拆分到独立文件中。
6.1 传统方式(Rust 2015 风格,仍可用)
// src/lib.rsmodfront_of_house;// 声明模块,内容在 src/front_of_house.rspubusecrate::front_of_house::hosting;pubfneat_at_restaurant(){hosting::add_to_waitlist();}// src/front_of_house.rspubmodhosting;// 声明子模块,内容在 src/front_of_house/hosting.rs// src/front_of_house/hosting.rspubfnadd_to_waitlist(){}6.2 现代方式(Rust 2018+,推荐)
使用与模块同名的目录 +mod.rs:
my_crate/ ├── src/ │ ├── lib.rs │ ├── front_of_house/ │ │ ├── mod.rs # front_of_house 模块内容 │ │ └── hosting.rs # hosting 模块内容// src/lib.rsmodfront_of_house;pubusecrate::front_of_house::hosting;pubfneat_at_restaurant(){hosting::add_to_waitlist();}// src/front_of_house/mod.rspubmodhosting;// src/front_of_house/hosting.rspubfnadd_to_waitlist(){println!("Added to waitlist");}6.3 另一种现代方式(不推荐 mod.rs)
Rust 2018+ 也支持目录名对应模块,但不推荐:
my_crate/ ├── src/ │ ├── lib.rs │ ├── front_of_house.rs # 模块根 │ └── front_of_house/ # 子模块目录 │ └── hosting.rs通常使用mod.rs方案更清晰。
七、pub use —— 重导出
pub use既可以导入项,又可以让外部访问它:
// lib.rsmodfront_of_house;pubusecrate::front_of_house::hosting;// 重导出pubfneat_at_restaurant(){hosting::add_to_waitlist();}外部使用者:
usemy_restaurant::hosting;// 直接使用重导出的模块fnmain(){hosting::add_to_waitlist();}八、Cargo.toml 与第三方依赖
8.1 添加依赖
[package] name = "my_project" version = "0.1.0" edition = "2021" [dependencies] rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0", features = ["full"] }8.2 使用第三方 crate
userand::Rng;useserde::{Serialize,Deserialize};fnmain(){letmutrng=rand::thread_rng();letn:u8=rng.gen();println!("随机数: {}",n);}九、完整项目示例
让我们创建一个餐厅管理系统来演示模块系统:
项目结构
restaurant/ ├── Cargo.toml └── src/ ├── main.rs ├── lib.rs ├── front_of_house/ │ ├── mod.rs │ └── hosting.rs └── back_of_house.rsCargo.toml
[package] name = "restaurant" version = "0.1.0" edition = "2021"src/lib.rs
modfront_of_house;modback_of_house;// 重导出常用项pubusecrate::front_of_house::hosting;pubusecrate::back_of_house::Breakfast;pubfneat_at_restaurant(){// 绝对路径crate::front_of_house::hosting::add_to_waitlist();// 相对路径front_of_house::hosting::seat_at_table();// 使用 Breakfastletmutmeal=Breakfast::summer("Rye");meal.toast=String::from("Wheat");println!("I'd like {} toast please",meal.toast);}src/front_of_house/mod.rs
pubmodhosting;pubfnseat_at_table(){println!("Seating at table");}src/front_of_house/hosting.rs
pubfnadd_to_waitlist(){println!("Added to waitlist");}fnseat_at_table(){// 私有函数println!("Seating at table (private)");}src/back_of_house.rs
pubstructBreakfast{pubtoast:String,// 公有字段seasonal_fruit:String,// 私有字段}implBreakfast{pubfnsummer(toast:&str)->Breakfast{Breakfast{toast:String::from(toast),seasonal_fruit:String::from("peaches"),}}}pubenumAppetizer{// 枚举的变体默认都是公有的Soup,Salad,}src/main.rs
userestaurant::{hosting,Breakfast};fnmain(){println!("=== 使用 restaurant crate ===");// 使用重导出的 hostinghosting::add_to_waitlist();// 使用 Breakfastletmutmeal=Breakfast::summer("Rye");meal.toast=String::from("Wheat");println!("Toast: {}",meal.toast);// meal.seasonal_fruit = String::from("blueberries"); // 错误:私有字段}十、常见陷阱与最佳实践
陷阱 1:忘记添加pub
modmy_module{fnprivate_function(){}// 私有,外部无法访问}fnmain(){my_module::private_function();// ❌ 编译错误}陷阱 2:路径混乱
// 不推荐:深层嵌套的路径usecrate::foo::bar::baz::qux::my_function;// 推荐:使用模块别名或重导出usecrate::foo::bar::baz::qux;qux::my_function();最佳实践总结
| 场景 | 推荐做法 |
|---|---|
| 小型项目 | 所有代码放在main.rs或lib.rs |
| 中型项目 | 按功能拆分到同目录的多个.rs文件 |
| 大型项目 | 使用目录 +mod.rs分层组织 |
| 暴露 API | 使用pub use重导出,隐藏内部结构 |
| 导入依赖 | 在Cargo.toml中声明,在代码中使用use |
十一、总结与速查
| 概念 | 语法 | 说明 |
|---|---|---|
| 定义模块 | mod module_name { ... } | 内联模块 |
| 声明外部模块 | mod module_name; | 模块在其他文件 |
| 公有性 | pub | 使项对外可见 |
| 绝对路径 | crate::module::item | 从 crate 根开始 |
| 相对路径 | super::item或self::item | 从父模块或当前模块开始 |
| 导入 | use path::to::item | 简化路径 |
| 重命名 | use path as alias | 解决冲突 |
| 重导出 | pub use path | 导入并对外暴露 |
核心要点:
- ✅
mod定义模块,控制私有性 - ✅ 路径可以是绝对(
crate::)或相对(self::/super::) - ✅
use将路径引入作用域 - ✅
pub use可以重导出 API - ✅ 大型项目应将模块拆分到多个文件
十二、思考题
- 下面代码能否编译?为什么?
modouter{fnprivate(){}pubmodinner{pubfncall(){super::private();// 子模块能调用父模块的私有函数吗?}}}fnmain(){outer::inner::call();}mod声明和use导入有什么区别?如何让外部 crate 用户只看到
my_crate::something,而看不到my_crate::internal::something?
参考链接
- Rust Book - Packages and Crates
- Rust Book - Modules
- Rust Book - Paths
- Rust Book - Use
- Rust Book - Separating Files