第一部分: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.rs
  • mod 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 访问。”

这样可以隐藏内部结构变化,提供一个干净、稳定的接口

用户不需要知道 ChessAppui 里。


第三部分:应用到 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)的典范。


第四部分:额外补充与实践建议

  1. Cargo 工作空间(workspace)

    • 当项目包含多个 Crate 时,可以在根目录定义一个 workspace:

      [workspace]
      members = ["chess_gui", "chess_server"]
      
    • 用于统一管理依赖与构建。

  2. 模块路径建议

    • 不建议模块层级太深,否则路径过长。
    • 若确实复杂,可用 pub use 或创建 “prelude 模块” 集中导出常用符号:

      pub mod prelude {
          pub use crate::board::Board;
          pub use crate::game::Game;
      }
      

      外部使用时:

      use chess_gui::prelude::*;
      
  3. 测试与文档

    • 每个模块可在文件底部添加:

      #[cfg(test)]
      mod tests {
          use super::*;
          #[test]
          fn it_works() {
              assert_eq!(2 + 2, 4);
          }
      }
      
    • 在顶层注释使用 /// 可生成文档注释,配合 cargo doc 浏览。

  4. 总结

    • lib.rs:定义和组织逻辑。
    • main.rs:使用逻辑。
    • mod:声明模块。
    • pub:控制可见性。
    • use:导入路径。
    • pub use:重导出公共接口。