项目结构与 Rust 程序心智模型
本章目标
这一章让你看懂一个 Rust 项目从磁盘文件到运行程序的基本关系。你会知道 src/main.rs 为什么重要,模块如何扩展项目,以及当前 RinBlog 为什么先把多数代码放在一个入口文件里。
它是什么
Rust 项目通常有这样的结构:
rinblog/
Cargo.toml
Cargo.lock
src/
main.rs
docs/
00-preface.md
Cargo.toml描述项目。Cargo.lock锁定依赖版本。src/main.rs是可执行程序入口。docs/是本书新增的 Markdown 教材目录。
如果一个项目是库,入口通常是 src/lib.rs。当前 RinBlog 是可执行 Web 服务,因此入口是 main.rs。
为什么需要
工程结构的目标不是把文件拆得很多,而是让变化有地方放。小项目放一个文件能降低学习成本,大项目必须拆模块,否则任何改动都会互相影响。
一个健康的 Rust 工程通常会逐步形成这些边界:
- 入口层:启动服务、读取配置。
- 路由层:根据请求路径分发。
- 领域层:表达业务概念。
- 存储层:读写文件或数据库。
- 展示层:生成 HTML 或 JSON。
当前项目先保持简单,再通过 src/docs.rs 演示如何把文档站能力拆出去。
怎么使用
你可以按调用顺序阅读当前程序:
main启动监听。handle_connection处理一个 TCP 连接。read_http_request解析 HTTP 请求。route_inner分发路径。- 渲染函数生成 HTML。
- 存储函数读写
data/posts。 docs::render_docs_response读取并渲染教材 Markdown。
读代码时先抓数据流:请求从哪里来,变成什么结构,最后怎么写回浏览器。
逐行解释
看一个最小模块声明:
mod docs;
fn main() {
println!("server starts");
}
mod docs;告诉编译器:当前模块有一个子模块叫docs。- 编译器会查找
src/docs.rs或src/docs/mod.rs。 fn main()仍然是程序入口。println!是输出宏,用来观察程序运行。
模块不是运行时加载的脚本,而是编译期组织代码的方式。mod docs; 不会“启动文档站”,它只是把 docs.rs 编进项目,让代码可以调用里面的函数。
常见坑
mod xxx;要写在父模块中,文件存在但没有声明时不会自动参与编译。- 文件名通常用小写和下划线,不用驼峰。
pub控制可见性。没有pub的函数默认只在当前模块及子模块内部使用。- 不要为了“架构感”过早拆很多文件。拆分应该服务于边界,而不是制造目录。
练习
- 新建
src/scratch.rs,写一个返回字符串的函数。 - 在
main.rs添加mod scratch;。 - 从
main调用这个函数并打印结果。 - 去掉函数的
pub,观察编译器如何提示可见性问题。
造轮子任务
写一个 notes 模块,里面定义 fn render_note(title: &str, body: &str) -> String,返回一段简单 HTML。然后从 main 调用它,理解“展示逻辑可以拆到模块里”。
小结
Rust 工程结构的第一原则是边界清楚。入口负责启动,模块负责组织能力,可见性负责控制依赖方向。后面的章节会不断回到这个心智模型:先让代码跑起来,再让边界长出来。