03 crate / module / 可见性(看懂项目结构与 mod

本章目标

  • 你能从 src/main.rs 一眼读懂“这个 crate 由哪些模块组成”。
  • 你能理解 modusepubpub use 的组合用法,并在项目里正确组织代码边界。
  • 你能在新增文件/目录后让编译器“找到它”,避免常见的模块路径错误。

术语速览(用你熟悉的语言类比)

  • crate:一个编译单元(类似一个 npm package / 一个 Maven 模块)
  • module:crate 内部的命名空间(类似 TS 的 module/namespace + 文件系统约定)
  • item 可见性pub 决定“外部模块是否可用”(类似 public/private,但粒度更细)

src/main.rs 读懂 crate 根

本项目的 crate root 是二进制入口:src/main.rs
你会看到类似这样的声明:

  • mod app;
  • mod components;
  • mod db;
  • mod models;
  • mod pages;
  • mod router;
  • mod services;
  • mod state;
  • mod utils;

这意味着:

  • crate 顶层模块包括:crate::appcrate::dbcrate::models
  • 对应文件/目录通常是:
    • src/app.rs(单文件模块)
    • src/db/mod.rs + src/db/*.rs(目录模块)

“目录模块”是怎么工作的(为什么需要 mod.rs

Rust 的模块系统会把文件系统映射为模块树:

  • src/db/mod.rs 对应模块:crate::db
  • src/db/asset_repo.rs 对应模块:crate::db::asset_repo

src/db/mod.rs 中,通过 mod asset_repo; 把子模块挂载进来,随后可以:

  • db/mod.rs 内部 use asset_repo::...
  • 或者 pub use asset_repo::AssetRepository; 作为对外 API(更常见)

use / pub / pub use 的组合(工程里最常用)

1) use:在当前模块引入名字

#![allow(unused)]
fn main() {
use crate::db::AssetRepository;
}

仅影响当前模块的可读性,不改变对外可见性。

2) pub:让 item 对外可见

#![allow(unused)]
fn main() {
pub struct AssetRepository;
}

其他模块才能通过 crate::db::asset_repo::AssetRepository 使用它。

3) pub use:重新导出(re-export)

这是大型项目里非常常见的“门面模式”:

  • 内部可以有 crate::db::asset_repocrate::db::plan_repo 等细粒度模块
  • 对外只暴露 crate::db::{AssetRepository, PlanRepository, SnapshotRepository}

你可以在 src/db/mod.rs 里看到类似写法:

  • pub use asset_repo::AssetRepository;
  • pub use plan_repo::PlanRepository;

这样 pages 层就不需要了解 db 的文件拆分细节。

路径系统:crate:: / super:: / self::

这是初学者经常迷路的地方,建议记住这三种“稳定定位”:

  • crate::xxx:从 crate 根开始(最稳)
  • super::xxx:从父模块开始(重构文件时容易受影响)
  • self::xxx:当前模块(用于更明确的内部引用)

src/models/asset_sub_category.rs 里就有典型用法:

  • use super::Category;:从同目录的父模块 models 引入 Category

常见报错与解决方式

1) file not found for module ...

原因通常是:

  • 新增了 src/foo/bar.rs,但没有在 src/foo/mod.rsmod bar;
  • 或者 mod 声明和文件名不一致

2) cannot find type/struct/function ... in this scope

原因通常是:

  • 忘了 use ... 引入
  • 或者 item 没有 pub,跨模块访问被禁止

本章练习(在项目里动手巩固)

练习 A:画出模块树(建议真的画出来)

src/main.rs 出发,把顶层模块写出来,然后继续展开:

  • dbconnectionasset_repoplan_reposnapshot_repo
  • modelsassetcategoryasset_scopevehicle_typeasset_sub_category
  • componentsassetplansnapshotanalysislayoutcommon

练习 B:新增一个 utils 子模块(不改业务)

  1. 新建文件:src/utils/debug.rs
  2. src/utils/mod.rs 增加:pub mod debug;
  3. debug.rs 写一个小函数:
    • pub fn dump_routes():打印路由列表(字符串即可)
  4. src/main.rs 或某个页面里临时调用它验证编译通过

你会经历一次完整的“新增模块 → 挂载 → 导入 → 使用”的流程。