# rust_test **Repository Path**: naoano/rust_test ## Basic Information - **Project Name**: rust_test - **Description**: rust_test - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-08 - **Last Updated**: 2026-08-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Rust 入门教学项目 给 Rust 初学者的手把手教学项目,包含 **5 个独立小项目**,从语法到实战循序渐进: | 项目 | 内容 | 难度 | 覆盖知识点 | |------|------|------|-----------| | [01_basics](#1-01_basics-基础语法教学) | 基础语法教学(9 章) | 入门 | 变量/函数/控制流/所有权/结构体/枚举/trait/模块/错误处理 | | [02_guessing_game](#2-02_guessing_game-猜数字) | 猜数字游戏 | 入门+ | 交互输入/随机数/循环/Result | | [03_student_manager](#3-03_student_manager-学生成绩管理) | 学生成绩管理 | 进阶 | 结构体/Vec/trait/三层模块拆分 | | [04_image_processor](#4-04_image_processor-图像批处理) | 图像批处理 | 进阶 | **文件系统**/image 库/命令行参数 | | [05_web_server](#5-05_web_server-网络服务器) | 网络服务器 | 进阶 | **网络**/HTTP 协议/框架对比 | --- ## 如何使用本教程 **如果你是零基础**,按下面顺序来,不要跳: ```mermaid graph LR A[01 语法] --> B[02 猜数字] B --> C[03 成绩管理] C --> D[04 图像处理] D --> E[05 网络] ``` **每个项目的正确学习姿势**: 1. 先看本 README 里该项目的"**学习目标**",知道学完要会什么 2. 运行程序,看效果(README 里有"**运行效果示例**",对照着看) 3. 打开该项目目录下的 **README.md**(逐文件代码导读 / 概念深挖与常见坑 / API 速查表 / 实验清单),跟着它读代码 4. 看"**知识点清单**"检查自己是否都掌握了 5. 做"**练习建议**"和子项目 README"**实验清单**"里的题目,卡住再回来看代码 > 最有效的学习方式:把代码里标注"取消注释会报错"的地方取消注释,亲眼看看编译器的报错。**Rust 的编译器报错是全世界最好的老师之一。** --- ## 环境准备 ### 1. 安装 Rust - 访问 下载安装程序 - 安装完成后**新开一个终端**,验证: ```powershell rustc --version # 例:rustc 1.97.1 cargo --version # 例:cargo 1.97.1 ``` ### 2. 进入项目 ```powershell cd E:\00__demo\02__rust\test_001 ``` ### 3. 常用命令 **方式一:进到子目录里运行**(推荐,最直观) ```powershell cd 01_basics cargo run # 运行语法教学 cargo test # 运行测试 ``` **方式二:在根目录用 `-p` 指定包名** ```powershell cargo run -p basics # 运行语法教学 cargo run -p guessing_game # 运行猜数字 cargo run -p student_manager # 运行成绩管理 cargo run -p image_processor -- 输入目录 输出目录 --mode grayscale cargo run --bin simple_server # 运行手写版服务器(需在 05_web_server 目录) cargo run --bin axum_server # 运行 axum 版服务器(需在 05_web_server 目录) cargo test # 运行全部测试 ``` > 注意:`-p` 后面跟的是**包名**(`Cargo.toml` 里 `[package] name` 字段),不是文件夹名。 > 所以 `01_basics` 文件夹的包名是 `basics`(包名不能以数字开头)。 --- ## 项目结构 ``` test_001/ ├── Cargo.toml # workspace 定义(管理多个子项目) ├── README.md # 本文件 ├── docs/ # 文档 │ └── superpowers/specs/ # 设计文档 ├── 01_basics/ # 项目 1:基础语法教学 │ ├── Cargo.toml │ └── src/ │ ├── main.rs # 入口:只调用各章演示,不写逻辑 │ ├── 01_variables.rs # 第 1 章:变量与数据类型 │ ├── 02_functions.rs # 第 2 章:函数 │ ├── 03_control_flow.rs # 第 3 章:控制流 │ ├── 04_ownership.rs # 第 4 章:所有权与借用 │ ├── 05_structs.rs # 第 5 章:结构体 │ ├── 06_enums.rs # 第 6 章:枚举与模式匹配 │ ├── 07_traits.rs # 第 7 章:trait 特征 │ ├── 08_modules.rs # 第 8 章:模块系统 │ └── 09_error_handling.rs # 第 9 章:错误处理 ├── 02_guessing_game/ # 项目 2:猜数字 │ ├── Cargo.toml # 依赖:rand(随机数) │ └── src/ │ ├── main.rs # 入口:输入输出 + 游戏循环 │ └── 01_game.rs # 游戏逻辑:随机数/比较/计次 ├── 03_student_manager/ # 项目 3:学生成绩管理 │ ├── Cargo.toml # 纯标准库,无外部依赖 │ └── src/ │ ├── main.rs # 入口:创建管理器,启动界面 │ ├── 01_student.rs # 学生数据模型(结构体 + 校验) │ ├── 02_manager.rs # 增删改查逻辑(Vec 操作) │ └── 03_ui.rs # 菜单交互(输入输出) ├── 04_image_processor/ # 项目 4:图像批处理 │ ├── Cargo.toml # 依赖:image(图像库) │ └── src/ │ ├── main.rs # 入口:命令行参数解析 │ └── 01_processor.rs # 核心:遍历目录/读图/处理/保存 └── 05_web_server/ # 项目 5:网络服务器 ├── Cargo.toml # 依赖:axum + tokio └── src/ ├── lib.rs # 公共内容:页面 + HTTP 解析器 ├── http/ # 手写 HTTP 解析器(可单测) │ ├── mod.rs │ ├── 01_request.rs # 解析请求行(方法、路径) │ └── 02_response.rs # 构造响应(状态码、头部、HTML) └── bin/ ├── simple_server.rs # 版 1:std::net 手写,零依赖 └── axum_server.rs # 版 2:axum 框架 ``` **组织思路**(也是真实 Rust 项目的通用思路): ```mermaid graph LR A[main.rs 入口] --> B[只做调用/调度] A --> C[模块 1:数据模型] A --> D[模块 2:业务逻辑] A --> E[模块 3:界面交互] B -. "职责分离" .-> F["每个文件只干一件事
逻辑可以写单元测试"] ``` **为什么这么分?** 因为"逻辑"和"界面"分开后,逻辑部分不用键盘鼠标也能测试(直接调函数),这就是所有项目都有 `cargo test` 能跑的原因。真实项目中这叫"可测试性设计"。 --- ## 1. 01_basics 基础语法教学 ### 学习目标 学完你应该能: - [ ] 说出 Rust 变量默认不可变,并会用 `mut` 和 `const` - [ ] 分清"表达式"(有值)和"语句"(没有值) - [ ] 用 `for` / `while` / `loop` / `match` 控制程序流程 - [ ] **用自己的话解释所有权三规则**(这是 Rust 与 C/Java 最大的不同) - [ ] 定义结构体并给它写方法(`impl`) - [ ] 用枚举 + `match` 处理"多种可能"的情况,理解 `Option` 是干什么的 - [ ] 定义一个 trait,并让多个类型实现它 - [ ] 说清 `pub` 的作用,会用 `use` 引入路径 - [ ] 写一个返回 `Result` 的函数,并知道 `?` 运算符的作用 ### 运行 ```powershell cd 01_basics cargo run ``` ### 运行效果示例(节选) ``` ========================================== Rust 基础语法教学(共 9 章) ========================================== ===== 第 1 章:变量与数据类型 ===== 1. 不可变变量 x = 5 第一次赋值后 y = 5 2. 可变变量 y 重新赋值为 6 3. 常量 MAX_POINTS = 100000 ... ===== 第 4 章:所有权与借用 ===== 1. 移动:s1 的所有权给了 s2,s2 = "hello" ... ``` ### 九章内容 | 章 | 文件 | 核心概念 | 对应经典书章节 | |----|------|---------|---------------| | 1 | 01_variables.rs | 不可变/可变变量、常量、基础类型、遮蔽 | 《Rust 程序设计语言》第 3 章 | | 2 | 02_functions.rs | 函数定义、参数、返回值、表达式 | 第 3 章 | | 3 | 03_control_flow.rs | if、loop、while、for、match | 第 3 章 | | 4 | 04_ownership.rs | **所有权、移动、克隆、借用**(最重要) | 第 4 章 | | 5 | 05_structs.rs | 结构体、方法、关联函数 | 第 5 章 | | 6 | 06_enums.rs | 枚举、模式匹配、Option | 第 6 章 | | 7 | 07_traits.rs | trait、默认方法、泛型约束、trait 对象 | 第 10 章 | | 8 | 08_modules.rs | 模块、可见性 pub、use 引入 | 第 7 章 | | 9 | 09_error_handling.rs | Option、Result、unwrap、? 运算符、panic | 第 9 章 | ### 每章文件的阅读方法 每个模块文件的结构都是: ``` 文件顶部:本章概念讲解(大段注释) ↓ demo() 函数:运行演示(代码带行注释) ↓ 文件底部:单元测试(学习怎么写测试) ``` **第 4 章(所有权)是全项目最重要的文件**,如果一遍看不懂,正常,正常人都要读两三遍 + 写几个小程序才懂。看懂它的标志:能回答"为什么 Rust 不需要垃圾回收器"。 ### 测试 ```powershell cd 01_basics cargo test ``` 每章都有单元测试,共 16 个。测试代码在文件底部 `mod tests` 里,`#[test]` 标记的函数就是测试,`assert_eq!` 断言"左右相等"。 ### 练习建议 1. 打开 01_variables.rs,把 `x = 6` 那行取消注释,编译看看报错,再注释回去 2. 给 02_functions.rs 加一个 `fn max(a: i32, b: i32) -> i32`(返回较大值),写个测试 3. 给 03_control_flow.rs 加一个 `for` 循环打印 1 到 10 的偶数 4. 改 04_ownership.rs 的 `take_ownership` 调用,让它借用而不是移动 5. 给 05_structs.rs 的 Rectangle 加一个 `fn scale(&self, factor: u32) -> Rectangle` 方法 --- ## 2. 02_guessing_game 猜数字 ### 学习目标 - [ ] 理解"程序 = 输入 → 处理 → 输出"的循环结构 - [ ] 会用 `loop` + `break` 写交互式主循环 - [ ] 会用 `trim()` + `parse()` 把字符串转数字,并处理失败 - [ ] 理解"逻辑模块"和"界面模块"为什么必须分开 - [ ] 看懂 `matches!` 宏在测试里的用法 ### 运行 ```powershell cd 02_guessing_game cargo run ``` ### 运行效果示例 ``` ========================================== 猜数字游戏 我已经想好了一个 1~100 之间的数字 你来猜猜看!输入 q 可以随时退出 ========================================== 请输入你的猜测: 50 太大了,再小一点! 请输入你的猜测: 25 太小了,再大一点! 请输入你的猜测: 37 恭喜!你用了 3 次就猜中了! 再来一局,新数字已生成! 请输入你的猜测: q 再见!本局你共猜了 0 次 ``` ### 玩法 - 程序随机想一个 1~100 的数字,你输入猜 - 猜小了提示"太小了",猜大了提示"太大了",猜中显示次数 - 输入 `q` 随时退出;猜中后自动开新一局 - 输入非数字(如 `abc`)会提示重新输入,不算次数 ### 代码结构(教学重点) ``` main.rs(界面) 01_game.rs(逻辑) 输入/输出 → 随机数生成 错误提示 → 大小比较 游戏循环 → 次数统计 ← 结果(GuessResult) ``` **为什么这样拆?** 因为 `01_game.rs` 的逻辑不碰键盘和屏幕,所以能写单元测试——直接调用 `game.guess(50)` 就能验证逻辑对不对,不需要真的玩一局。 ### 知识点清单 | 代码 | 知识点 | |------|--------| | `rand::thread_rng().gen_range(1..=100)` | 第三方库 rand 的用法 | | `io::stdin().read_line(&mut input)` | 标准输入读取 | | `input.trim().parse::()` | 字符串 → 数字,返回 `Result` | | `match num.cmp(&self.secret)` | `Ordering` 枚举比较 | | `matches!(result, GuessResult::TooBig)` | 测试里判断枚举变体 | ### 用到的依赖 - `rand`(随机数)—— 你第一次接触"第三方库": 1. 在 `Cargo.toml` 的 `[dependencies]` 里写 `rand = "0.8"` 2. `cargo build` 时会自动下载 3. 代码里 `use rand::Rng;` 即可使用 4. 这是 Rust 生态的核心工作流:装依赖 = 改一行 Cargo.toml ### 测试 ```powershell cd 02_guessing_game cargo test ``` 测试覆盖:猜中、猜大、猜小、非法输入不计次、游戏结束拒绝再猜。注意测试里造了一个"固定秘密数字"的游戏(`game_with_secret`),这就是"逻辑独立"带来的好处——测试可以控制一切。 ### 练习建议 1. 改 `gen_range(1..=100)` 为别的范围,比如 1~1000,游戏难度变化 2. 猜中后显示"太棒了!(≤5次)/ 不错 / 继续加油"的评价 3. 给 `Game` 加一个"最快纪录"字段,一局结束更新它 --- ## 3. 03_student_manager 学生成绩管理 ### 学习目标 - [ ] 会用结构体组织数据,并用 `Result` 做输入校验 - [ ] 掌握 `Vec` 的常用操作:push / remove / iter / find / retain - [ ] 实现 `Display` trait 自定义打印格式 - [ ] 理解"数据模型 / 业务逻辑 / 界面交互"三层拆分 - [ ] 理解借用:`get()` 返回引用,`iter_mut()` 才能修改 ### 运行 ```powershell cd 03_student_manager cargo run ``` ### 运行效果示例 ``` 欢迎使用学生成绩管理系统! (数据只保存在内存中,退出程序后会清空) ========== 学生成绩管理系统 ========== 1. 添加学生 2. 查看所有学生 3. 删除学生 4. 修改成绩 5. 统计信息(平均分/最高分) q. 退出 ===================================== 请输入你的选择: 1 --- 添加学生 --- 学号: 001 姓名: 小明 成绩(0~100): 90 添加成功! 请输入你的选择: 1 --- 添加学生 --- 学号: 001 姓名: 小红 成绩(0~100): 80 添加失败: 学号 001 已存在 请输入你的选择: 5 --- 统计信息 --- 学生人数: 1 平均分: 90.0 最高分: 学号: 001, 姓名: 小明, 成绩: 90.0 分(优秀) 请输入你的选择: q 再见! ``` ### 功能 - 添加学生(学号、姓名、成绩,带输入校验:学号姓名非空、成绩 0~100、学号不重复) - 查看所有学生(含成绩等级:优秀/良好/及格/不及格) - 删除学生、修改成绩 - 统计信息(人数、平均分、最高分) - 输入 `q` 退出 > 注意:数据只存在内存里,程序退出就清空。这是故意的——先学会内存操作,后面再学文件/数据库持久化。 ### 代码结构(教学重点:三层拆分) | 文件 | 职责 | 类比 | 你以后会遇到的对应物 | |------|------|------|---------------------| | `01_student.rs` | 学生长什么样(数据 + 校验) | "表结构" | 数据库表 / ORM 模型 | | `02_manager.rs` | 增删改查怎么算(业务逻辑) | "服务层" | DAO / Repository | | `03_ui.rs` | 菜单和输入输出(界面) | "控制层" | Controller / ViewModel | ```mermaid graph TD U[03_ui.rs 菜单交互] --> M[02_manager.rs 增删改查] M --> S[01_student.rs 数据模型] M -. "每个函数都能单测" .-> T[单元测试] ``` **信息流**:ui 只负责"问用户要什么、显示结果",真正的判断和修改全在 manager;manager 只操作 Student 数据,不知道"菜单"的存在。这样以后把 ui 换成网页界面,manager 一行都不用改。 ### 知识点清单 | 代码 | 知识点 | |------|--------| | `#[derive(Debug, Clone, PartialEq)]` | 自动实现 trait,少写大量样板代码 | | `impl std::fmt::Display` | 自定义 `{}` 打印格式 | | `self.students.iter().any(...)` | 遍历检查是否存在 | | `position()` + `remove(index)` | 按条件删除 | | `iter_mut().find(...)` | 找到并**修改**元素(可变借用) | | `.max_by(|a, b| a.score.total_cmp(&b.score))` | 按字段找最大值 | ### 测试 ```powershell cd 03_student_manager cargo test ``` 测试覆盖:学生创建校验、成绩等级、增删改查、平均分、最高分、重复学号拒绝。 ### 练习建议 1. 加一个"按姓名搜索"功能(提示:manager 加 `fn search_by_name`,ui 加菜单项) 2. 加一个"按成绩排序显示"功能(提示:`Vec::sort_by`) 3. 统计不及格人数(提示:`.filter()` + `.count()`) 4. 把数据保存到文件里(提示:`std::fs::File` + `serde_json`) --- ## 4. 04_image_processor 图像批处理 ### 学习目标 - [ ] 会用 `std::fs::read_dir` 遍历目录 - [ ] 会用 `Path` / `PathBuf` 拼接路径、取扩展名、取文件名 - [ ] 会用 `std::fs::create_dir_all` 创建目录 - [ ] 会用 image 库读图、处理、保存 - [ ] 会用 `std::env::args` 手写命令行参数解析 ### 运行 ```powershell cd 04_image_processor cargo run -- photos output # 批量转灰度 cargo run -- photos output --mode invert # 批量反色(底片效果) cargo run -- photos output --mode resize --width 320 # 批量缩放 ``` - `photos` 换成你的图片文件夹(支持 png/jpg/jpeg/bmp/gif/webp/tiff) - `output` 是输出目录,不存在会自动创建 - 处理结果保存在输出目录,文件名与原图一致 - `--` 是 cargo 的分隔符:告诉 cargo"后面的参数是给程序本人的" ### 运行效果示例 ``` 输入目录: photos 输出目录: output 处理模式: 灰度 已处理: 风景.png 已处理: 猫猫.jpg 跳过 说明.txt: 解码失败(可能不是有效图片) 处理完成,共处理 2 张图片 ``` > 注:上例中 `说明.txt` 因为扩展名不是图片而被跳过(看代码里 `is_image_file` 的逻辑)。 ### 代码结构 ``` main.rs(界面) 01_processor.rs(逻辑) 命令行参数解析 → 遍历目录(read_dir) 模式/宽度处理 → 读图(ImageReader) → 处理(灰度/反色/缩放) → 保存(save) ``` ### 文件系统教学点(这是本章重点) | 代码 | 作用 | 对应"人类语言" | |------|------|---------------| | `std::fs::read_dir(input)` | 打开目录,得到一个"条目迭代器" | 打开文件夹,看里面有什么 | | `entry.path()` | 条目的完整路径 | 每个文件/文件夹的地址 | | `path.is_file()` | 判断是不是文件(不是文件夹) | 是文件还是文件夹? | | `path.extension()` | 取扩展名(返回 `Option<&OsStr>`) | 这个文件是什么类型? | | `path.file_name()` | 取文件名 | 这文件叫什么? | | `output.join(file_name)` | 拼接路径 | 输出目录 + 文件名 = 输出路径 | | `std::fs::create_dir_all` | 递归创建目录 | 没有文件夹?一路建出来 | | `?` 运算符 | 出错直接返回错误给上层 | 不行就甩锅给调用者 | ### 图像处理库 项目用的是 **image** 库(纯 Rust 实现,最流行的 Rust 图像库)。 > **为什么不用 OpenCV?** Rust 有 `opencv-rust`(OpenCV 的绑定),功能最强,但需要在系统装 OpenCV C++ 库,Windows 上配置麻烦、编译慢,不适合初学者。image 库读写 PNG/JPG 等常见格式完全够用,一条依赖搞定。 image 库的核心三步(也是所有图像处理的套路): ```rust // 1. 打开并解码 let img = ImageReader::open(path)?.decode()?; // 2. 处理(grayscale/invert/resize/裁剪/旋转...) let out = img.grayscale(); // 3. 保存(按扩展名自动选格式) out.save(output_path)?; ``` ### 测试 ```powershell cd 04_image_processor cargo test ``` 测试会临时生成图片验证:灰度转换(红色变灰 54,Rec.709 公式)、反色(红变青)、缩放(尺寸变化)、批量处理端到端、不存在的目录报错。 ### 练习建议 1. 让程序**递归处理子目录**(提示:处理到目录时调用自己) 2. 加一个"加边框"模式(提示:image 库有 `imageops::overlay` 可以贴图) 3. 处理时给输出文件加后缀(如 `a_gray.png`),避免覆盖原目录 4. 给 `--mode` 加一个"blur"模糊模式(提示:`img.blur(sigma)`) --- ## 5. 05_web_server 网络服务器 ### 学习目标 - [ ] 理解 HTTP 协议的最基本格式(请求行 / 响应状态行 / 响应头) - [ ] 理解浏览器和服务器之间发生了什么(连接 → 请求 → 响应 → 断开) - [ ] 会看响应头里的 `Content-Length` 和 `Content-Type` - [ ] 理解"框架"和"手写"的关系:框架替你做了什么 - [ ] 会读 axum 的声明式路由代码 ### 运行 同一个网站,**两种方式**实现: | 方式 | 运行命令(在 05_web_server 目录) | 依赖 | |------|------|------| | 手写版 | `cargo run --bin simple_server` | 零依赖(std) | | axum 版 | `cargo run --bin axum_server` | axum + tokio | 启动后浏览器打开 ,两个版本功能完全一样: - `/` 首页 - `/hello` 动态问候页(带时间戳) - 其他路径 → 404 页面 > 两个服务器都用 8080 端口,**不要同时启动**。 ### 运行效果示例(浏览器里看到的) **`/` 首页**:显示"你好,Rust 网络世界!"和两个链接。 **`/hello` 问候页**:显示"你好,访客!"和一个数字时间戳——每次刷新数字都变,证明页面是服务器动态生成的。 **任意其他路径**:显示"404 页面不存在"。 用浏览器开发者工具(F12 → 网络标签)看 `/hello` 的响应头,能看到: ``` HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 214 ``` ### 代码结构 ``` lib.rs 两个版本共用的页面 + HTTP 解析器 ├── http/01_request.rs 解析请求行(方法、路径)—— 可单测 ├── http/02_response.rs 构造响应(状态码、Content-Length)—— 可单测 ├── bin/simple_server.rs 手写版:TCP 监听 → 读请求 → 路由 → 写响应 └── bin/axum_server.rs axum 版:声明式路由 + 异步处理 ``` ### 手写版教你什么:HTTP 协议长什么样 手写版完整走了一遍 HTTP 协议链路(浏览器和服务器之间最底层的对话): ```mermaid sequenceDiagram participant B as 浏览器 participant S as 服务器 B->>S: TCP 连接建立 B->>S: 请求行: GET /hello HTTP/1.1 S-->>S: 解析请求行 → 得到方法+路径 S-->>S: 路由匹配 → 构造响应 S-->>B: 响应: HTTP/1.1 200 OK + 头部 + HTML B-->>B: 渲染页面 ``` **HTTP 请求的第一行**(请求行)长这样: ``` GET /hello HTTP/1.1 │ │ │ │ │ └─ 协议版本 │ └──────── 路径 └───────────── 方法(GET=获取) ``` **HTTP 响应的第一行**(状态行)长这样: ``` HTTP/1.1 200 OK │ │ │ │ │ └─ 原因短语 │ └──── 状态码(200=成功, 404=不存在, 400=格式错) └───────────── 协议版本 ``` ### 两个版本对比 | 对比项 | 手写版(simple_server) | axum 版(axum_server) | |--------|----------------------|---------------------| | 代码量 | ~70 行(不含解析器) | ~40 行 | | 路由 | 手写 match 分支 | 声明式 `.route("/", get(...))` | | 请求解析 | 手写解析请求行 | 自动解析,handler 参数直接拿数据 | | 响应构造 | 手写拼字符串 | 返回 `Html(...)` 自动生成 | | 404 | 手写兜底分支 | 自动 + `.fallback()` 自定义 | | 并发 | 每个连接一个线程 | 异步运行时(tokio),开销小得多 | | 错误处理 | 手工处理每步错误 | 框架帮你分类处理 | | 生产可用 | 缺超时/限流/日志/压缩 | 生态齐全(中间件随便加) | **框架替你做了什么**(这是对比的核心收获): ```mermaid graph LR A[同样的一句话
'GET /hello HTTP/1.1'] --> B[手写版
手动拆字符串
手动拼响应
手动管线程] A --> C[axum 版
自动解析成数据结构
自动生成响应头
自动调度异步任务] ``` 简单说:**手写版让你知道框架在你背后干的活,axum 版让你看到"把活交给框架"后代码有多干净**。以后你用任何框架(不止 Rust),都会知道框架帮你处理了 TCP、解析、并发这些脏活。 ### 测试 ```powershell cd 05_web_server cargo test ``` 测试覆盖 HTTP 解析器:正常请求行、POST、空行、缺路径的畸形行、Content-Length 字节数计算、404 响应格式。 ### 练习建议 1. 手写版加一个 `/time` 路径,返回当前时间 2. 给手写版加"请求头读取"(提示:`BufReader.lines()` 循环读到空行) 3. axum 版加一个带参数的路径:如 `/user/张三`(提示:axum 的 `Path` 提取器) 4. 用浏览器开发者工具(F12)对比两个版本返回的响应头 5. 手写版改用 `TcpListener::set_nonblocking` 试试,感受为什么要异步 --- ## 教学路线(建议顺序) ```mermaid graph LR A[第 1 步:装环境] --> B[第 2 步:跑 01_basics] B --> C[第 3 步:逐章读源码改代码] C --> D[第 4 步:玩 02_guessing_game] D --> E[第 5 步:研究 01_game.rs 的测试] E --> F[第 6 步:用 03_student_manager] F --> G[第 7 步:做练习改功能] G --> H[第 8 步:04_image_processor 学文件系统] H --> I[第 9 步:05_web_server 学网络] ``` 1. **装好环境**,确认 `cargo --version` 能用 2. **跑 01_basics**,把所有章节输出看一遍 3. **逐章读源码**:重点第 4 章(所有权),读不懂很正常,多读几遍 4. **玩猜数字**,理解"输入 → 逻辑 → 输出"的循环 5. **看 01_game.rs 的测试**,理解"逻辑和界面分离"为什么好 6. **用学生管理系统**,体验完整的小项目 7. **做练习**:README 和代码注释里都给了改造建议 8. **图像批处理**:学文件系统操作,拿自己的照片练手 9. **网络服务器**:先跑手写版,再跑 axum 版,理解框架的价值 --- ## PDF 文档(与 README 同步) 项目提供 `docs/README.pdf`,是本文档的 PDF 版(含全部图表渲染)。 **同步机制**:README.md 更新后,重新运行转换脚本即可,无需改任何代码: ```powershell node scripts/md2pdf/md2pdf.js ``` **脚本原理**(3 步,全本地执行,不依赖网络): ```mermaid graph LR A[README.md] --> B[marked 渲染成 HTML
含 mermaid 图] B --> C[Edge 无头模式打开页面
mermaid 把图画出来] C --> D[docs/README.pdf] ``` - 依赖:`scripts/md2pdf/` 下 `npm install marked mermaid`(只需装一次) - 原理:marked 把 Markdown 变成 HTML → Edge 无头模式(`--print-to-pdf`)打印成 PDF → mermaid 图用本地 JS 渲染,`--virtual-time-budget` 等待渲染完成 - 支持自定义输入输出:`node md2pdf.js 其他文件.md 输出.pdf` --- ## 常见问题 **Q: 中文注释/输出会乱码吗?** A: 终端用 UTF-8 编码一般没问题。Windows 老终端(GBK)下运行,可以先执行 `chcp 65001` 切换到 UTF-8。 **Q: `cargo run` 提示找不到包?** A: 确认是在子目录(`01_basics` 等)里运行;在根目录则要用 `cargo run -p 包名`(注意是包名不是文件夹名)。 **Q: 编译报错看不懂?** A: 编译器的报错信息是最好的老师。先看最上面的红色错误行,再往下翻,通常有"help"提示和修改建议。**报错不是失败,是编译器在教你怎么写。** **Q: 端口 8080 被占用?** A: 先停掉另一个服务器,或者改代码里的 `127.0.0.1:8080` 为其他端口如 `8081`。 **Q: 想从头自己建一个这种项目?** A: 学习 `cargo new`: ```powershell cargo new my_project # 新建二进制项目 cargo new my_lib --lib # 新建库项目 cargo init # 在现有目录里初始化 ``` **Q: `cargo run -- photos output` 里的 `--` 是什么?** A: `--` 是分隔符:它前面的参数给 cargo,后面的参数给程序本身。没有它,cargo 会把 `photos` 当成自己的参数。 **Q: 学完这个项目下一步学什么?** A: 推荐路线:官方书《Rust 程序设计语言》(rust-lang.org 免费)精读所有权和生命周期 → 学 `serde`(序列化)和 `tokio`(异步) → 用 `axum` 写一个带数据库的完整项目 → 看 `rustlings` 练习题库(约 90 道小练习)巩固。