第一部分:Rust 的基石 —— Crates(包)
在 Rust 世界中,所有代码都被组织在 Crate(包)中。
Crate 是 Rust 编译器一次性编译的最小代码单元,相当于一个完整的项目、一个库,或一个可执行程序。
Rust 的包管理由 Cargo 负责,它同时管理 Crate 的构建、依赖和发布。
一、Crate 的两种类型
1. Binary Crate(二进制包)
目的:编译成一个可执行程序。 就像 Windows 的 .exe 或 Linux/macOS 下的可执行文件。
特征:
- 必须有一个
fn main() { ... }函数,作为程序的唯一入口点。 - “Crate Root”(根文件)为
src/main.rs。 - 通过
cargo run可以直接运行。
例子:
// src/main.rs
fn main() {
println!("Hello, Rust!");
}
这是最简单的二进制包。
更复杂的如命令行工具、GUI 启动器等,也都是 Binary Crate。
2. Library Crate(库包)
目的:提供一组可复用的功能,不能直接运行。 类似于 C++ 的 .lib/.dll 或 Java 的 .jar。
特征:
- 没有
main()。 - “Crate Root”为
src/lib.rs。 - 可以通过
pub关键字公开模块、函数、结构体等。 - 可以被其他项目引用(例如通过
Cargo.toml的[dependencies])。
例子:
// src/lib.rs
pub fn greet(name: &str) {
println!("Hello, {name}!");
}
在另一个 Crate 中使用:
use mylib::greet;
fn main() {
greet("World");
}
二、一个项目可以同时拥有两种 Crate 吗?
可以,而且非常常见。
这是一种优秀的项目架构模式,尤其在大型项目中。
例子:chess_gui 项目结构
chess_gui/
├── src/
│ ├── main.rs ← Binary Crate(启动入口)
│ ├── lib.rs ← Library Crate(核心逻辑)
│ ├── board.rs
│ ├── game.rs
│ ├── ai.rs
│ └── ui.rs
└── Cargo.toml
作用划分:
lib.rs包含核心逻辑、模块划分、公共接口。main.rs只负责启动程序,调用库中封装好的功能。
第二部分:模块系统 —— 组织你的代码
如果说 Crate 是“一个项目”,那么模块(Module)系统就是项目的章节目录结构。
一、声明模块(mod)
使用 mod 声明模块时,Rust 会自动寻找对应的文件。
规则:
mod board;→ 加载src/board.rsmod ui;→ 加载src/ui.rs- 若模块是一个文件夹:
mod board;→ 加载src/board/mod.rs
例子(在 src/lib.rs 中):
pub mod types;
pub mod board;
pub mod ai;
pub mod ui;
pub mod game;
这让整个库的结构清晰可读。每个模块都有自己的职责。
二、控制可见性(pub)
Rust 默认一切都是私有的。
想让其他模块访问某个模块或成员,必须显式使用 pub。
例子:
// lib.rs
pub mod game; // 允许外部访问 game 模块
// board.rs
pub struct Board {
pub width: usize, // 外部可见
height: usize, // 外部不可见
}
区别说明:
mod game;:仅库内部可访问。pub mod game;:外部 Crate(例如 main.rs)也能访问chess_gui::game。
三、简化路径(use 与 pub use)
Rust 模块访问是基于路径的(类似文件路径)。
如果模块层级太深,可以用 use 创建简写。
1. use(本地快捷方式)
例子:
// 在 game.rs 中
use crate::board::Board;
fn start_game() {
let board = Board::new();
}
这里的 crate 表示当前 Crate 根(即 lib.rs)。
2. pub use(重新导出)
pub use 不仅是简写,还会把引用暴露到公共 API。
例子:
// lib.rs
pub use ui::ChessApp;
含义:
“我知道 ChessApp 在 ui 模块中,但我希望外部用户能直接通过
chess_gui::ChessApp访问。”
这样可以隐藏内部结构变化,提供一个干净、稳定的接口。
用户不需要知道 ChessApp 在 ui 里。
第三部分:应用到 chess_gui 项目
我们用“造汽车”的比喻来理解这一切。
一、src/lib.rs —— 汽车工厂(Library Crate)
目的: 负责生产一辆完整的汽车(ChessApp)。
工厂的部门分工:
| 文件 | 部门职能 |
|---|---|
board.rs | 底盘和棋盘结构 |
game.rs | 游戏引擎和逻辑 |
ai.rs | 自动驾驶(AI 对手) |
ui.rs | 车身、控制面板、显示界面 |
pub mod 就像“部门公告”:
pub mod game;
pub mod ui;
表示这些部门存在且对外公开。
pub use 就像展厅展示成品:
pub use ui::ChessApp;
用户不需要知道内部结构,只需看到成品 ChessApp。
二、src/main.rs —— 汽车钥匙与司机(Binary Crate)
职责: 启动并使用由工厂生产的汽车。
示例:
// src/main.rs
use chess_gui::ChessApp;
use eframe::run_native;
fn main() {
run_native(
"Chess GUI",
eframe::NativeOptions::default(),
Box::new(|_| Box::new(ChessApp::default())),
);
}
main.rs 不需要知道底层模块,只需调用公开接口。
这是关注点分离(Separation of Concerns)的典范。
第四部分:额外补充与实践建议
-
Cargo 工作空间(workspace)
-
当项目包含多个 Crate 时,可以在根目录定义一个 workspace:
[workspace] members = ["chess_gui", "chess_server"] -
用于统一管理依赖与构建。
-
-
模块路径建议
- 不建议模块层级太深,否则路径过长。
-
若确实复杂,可用
pub use或创建 “prelude 模块” 集中导出常用符号:pub mod prelude { pub use crate::board::Board; pub use crate::game::Game; }外部使用时:
use chess_gui::prelude::*;
-
测试与文档
-
每个模块可在文件底部添加:
#[cfg(test)] mod tests { use super::*; #[test] fn it_works() { assert_eq!(2 + 2, 4); } } -
在顶层注释使用
///可生成文档注释,配合cargo doc浏览。
-
-
总结
lib.rs:定义和组织逻辑。main.rs:使用逻辑。mod:声明模块。pub:控制可见性。use:导入路径。pub use:重导出公共接口。