Rustlings 贡献指南:从提 Issue 到落地一道新练习的完整实操路径
本文基于 Rustlings 仓库的 CONTRIBUTING.md 展开,系统讲解该项目的贡献规则、Issue 与 Pull Request 流程,以及新增练习的完整操作步骤:从文件命名、info.toml 元数据编写,到 dev check 自动校验与 CI 准入条件。读完后,你可以独立完成从报告 Bug 到提交并合并一道全新 Rust 练习的整个贡献闭环,并清楚每一道官方质量闸门背后由哪些源码逻辑把关。
贡献总原则:先沟通,再动手
Rustlings 对贡献者最核心的一条要求写在 CONTRIBUTING.md 的 Pull Requests 一节中:除非你的改动非常小且显而易见(small and trivial),请先开一个 Issue 讨论你的想法,再提交 Pull Request。仓库方鼓励贡献者耐心等待评审,也欢迎在遇到 Git 相关问题时直接求助。
文档中给出了一个按意图分流的快速参考表,贡献前可先对号入座:
| 我想做…… | 推荐路径 |
|---|---|
| 报告一个 Bug | 打开一个 Issue(见下文 Issues 一节) |
| 修复一个 Bug | 直接打开 Pull Request |
| 实现一个新功能 | 先开 Issue 讨论,再开 Pull Request |
| 添加一道新练习 | 按“新增练习”流程操作(见下文核心章节) |
| 更新一道过时练习 | 打开 Pull Request,并同步检查对应答案 |
其中最后一条值得特别留意:更新一道练习时,必须检查它的答案(solution)是否也需要同步更新。这个要求不是口头约定,而是由仓库的自动化检查强制执行——dev check --require-solutions 会实际编译并运行 solutions/ 目录下的每个答案文件,详见“校验闸门”一节。
LLM 使用政策
CONTRIBUTING.md 明确列出了贡献过程中使用 LLM(大语言模型)的三条边界:
- ✅ 允许:个人使用,例如调研、辅助调试;
- ⚠️ 必须披露:完全或部分由 LLM 生成的代码贡献需要主动说明,维护者对这类贡献的评审意愿可能更低;
- ❌ 禁止:用 LLM 生成评论、Issue 或 PR 描述。原文的理由是:“我们想和你本人对话,而不是和你的 LLM 对话。”
该政策参照了 Rust 官方团队(rust-lang)发布的 LLM Usage Policy,体现了仓库对人机协作贡献的审慎态度:工具可以辅助,但沟通主体必须是贡献者本人。
报告 Bug:Issue 里必须包含什么
报告 Bug 时,CONTRIBUTING.md 要求 Issue 中附上以下命令的输出与环境信息:
- cargo --version
- rustlings --version
- ls -la
- 操作系统的名称和版本
这四条信息分别对应复现所需的关键维度:Cargo 工具链版本、Rustlings 程序版本、工作目录的实际内容(比如是否误在错误的目录运行了 rustlings)、以及操作系统。
为什么操作系统维度不可省略?从 CI 工作流 可以看到,cargo test --workspace 是在 ubuntu-latest、windows-latest、macos-latest 三个平台上按矩阵并行运行的,说明仓库把跨平台行为视为一等公民。贡献者在三个平台上的命令输出、文件权限行为可能存在差异,提供 OS 信息能帮助维护者直接缩小排查范围。
Pull Request:fork、提交与评审节奏
文档说明了 PR 的基本流程:fork 仓库、提交改动。对于练习类改动,有一条容易遗漏的配套动作——练习与其答案必须保持一致:
- 练习位于
exercises/<目录>/<练习名>.rs; - 对应答案位于
solutions/<目录>/<练习名>.rs。
例如修改 exercises/01_variables/variables1.rs 时,就要同步检查 solutions/01_variables/variables1.rs。如果 PR 只改了练习而答案仍然能通过全部检查,dev check 的“答案必须可运行”这一关就会替你兜底;反之如果改练习时把答案改坏了,PR 在 CI 阶段就会直接失败。
从 CI 工作流 可以确认,每个 PR 会依次经过:cargo clippy -- --deny warnings、cargo fmt --all --check、三平台 cargo test --workspace、cargo dev check --require-solutions,以及 Markdown 风格检查(rumdl)。也就是说,你本地跑一遍 dev check 通过,基本就覆盖了 CI 的绝大部分准入条件——这一点在后文会展开。
新增一道练习:完整操作步骤
这是 CONTRIBUTING.md 中篇幅最大、也最实用的部分。新增练习的五个步骤依次为:命名规范 → 编写练习文件 → 编写 README → 编写答案 → 注册元数据,最后提交 PR。下面逐一展开,并结合源码说明每一步的“硬性依据”。
第一步:文件命名
- 练习文件命名为
exercises/yourTopic/yourTopicN.rs,例如exercises/yourTopic/yourTopic1.rs; - 在
exercises/yourTopic/README.md中放一些有用的链接,并链接到 The Book(Rust 官方《The Rust Programming Language》书籍)的相关章节; - 在练习代码中,在需要用户修改的位置添加
// TODO: …注释。
README 的作用是承接 The Book 之外的延伸阅读。可参考现有练习目录的写法,如 exercises/00_intro/README.md:先一句话说明本节主题(print! 与 println! 宏),再在 “Further information” 小节列出参考链接。
// TODO 注释不是格式偏好,而是强制要求:dev check 会逐字扫描每个练习文件,找不到 // TODO 就直接报错 “You need to have at least one such comment to guide the user”。其源码逻辑位于 src/dev/check.rs 的 check_info_file_exercises 函数中。
第二步:编写答案
- 在
solutions/yourTopic/yourTopicN.rs添加答案文件,并用注释解释做法。
答案文件的路径规则与练习严格镜像:练习是 exercises/<dir>/<name>.rs,答案就是 solutions/<dir>/<name>.rs。这一对应关系在源码中由 src/exercise.rs 的 sol_path() 方法确定——它根据元数据里的 dir 和 name 拼出答案路径,dir 缺省时退化为 solutions/<name>.rs。
第三步:在 info.toml 中注册元数据
在 rustlings-macros/info.toml 文件中为练习添加元数据(官方练习集合的元数据统一维护在这个文件中),格式如下:
[[exercises]]
name = "yourTopicN"
dir = "yourTopic"
hint = """
A useful (multi-line) hint for your exercise.
Include links to a section in The Book or a documentation page."""
如果练习不含任何测试,就在元数据中加 test = false;但文档同时强调推荐添加测试。
元数据字段的完整语义
对照 src/info_file.rs 中 ExerciseInfo 结构体的反序列化定义,每个字段的默认值和含义如下:
| 字段 | 是否必填 | 默认值 | 含义 |
|---|---|---|---|
name |
是 | 无 | 练习名,即去掉 .rs 后缀的文件名 |
dir |
否 | 无 | 练习在 exercises/ 下的目录名;缺省时练习路径为 exercises/<name>.rs |
test |
否 | true |
是否运行 cargo test;置 false 后练习编译通过即视为完成 |
strict_clippy |
否 | false |
是否要求 Clippy 零警告才算通过 |
hint |
是 | 无 | 用户输入 h 时显示的多行提示 |
skip_check_unsolved |
否 | false |
跳过“练习未解决”检查(供本来就是解决状态的介绍性练习使用) |
几个关键细节:
- 练习路径由
name+dir拼出:ExerciseInfo::path()(src/info_file.rs)在有dir时生成exercises/<dir>/<name>.rs,无dir时生成exercises/<name>.rs。所以元数据里的name必须与磁盘上的文件名完全一致,否则dev check打开文件时就会报 “Failed to open the file”。 test与#[test]必须自洽:test = false但文件里存在#[test],或者test为true(默认)但文件里没有#[test],两者都会让dev check失败。这条规则保证了“元数据声明的行为”和“文件实际内容”不会出现偏差。skip_check_unsolved的真实用途:dev check会实际运行每个练习以确认它“尚未被解决”(防止提交人把答案误写进练习文件)。像 intro1 这种本来就要求编译通过的介绍性练习,通过在元数据中加skip_check_unsolved = true来豁免该检查——可以在 rustlings-macros/info.toml 开头看到官方练习正是这样配置的。
官方练习与社区练习的元数据位置差异
CONTRIBUTING.md 要求官方练习的元数据加到 rustlings-macros/info.toml,这是因为官方练习集合在构建 Rustlings 二进制时被嵌入(见 src/embedded.rs 的引用与 src/info_file.rs 中的 EMBEDDED_FILES.info_file)。运行时,InfoFile::parse() 的行为是:当前目录存在本地 info.toml 就优先解析它,否则回退到内嵌的官方练习表。
这个设计意味着社区练习仓库(通过 rustlings dev new <path> 脚手架生成,实现见 src/dev/new.rs)把元数据直接放在仓库根目录的 info.toml 中,而官方仓库则维护 rustlings-macros/info.toml 这一内嵌源——两条路径共用同一套 ExerciseInfo 结构与校验逻辑。当前官方练习表的规模可从 rustlings-macros/info.toml 直接确认:文件以 format_version = 1 开头,并包含 94 个 [[exercises]] 条目。
第四步:让 Cargo 识别新二进制
新增练习文件后,不要手工往 Cargo.toml 的 bin 列表里加条目——官方仓库的 Cargo.toml 头部就注明“bin 列表由 rustlings dev update 自动更新,勿手工编辑”。运行:
rustlings dev update
即可根据 info.toml 重新生成 bin 列表。从 src/dev/update.rs 的实现可以看到:该命令解析 InfoFile 后重写 Cargo.toml 的 bin 段;在以源码调试构建(debug build)开发 Rustlings 本体时,它改写的则是 dev/Cargo.toml,且练习路径前缀为 ../(因为 dev crate 位于仓库内一层子目录)。相应地,dev check 会比对 Cargo.toml 的 bin 段是否与 info.toml 推导结果一致,不一致就报错 “Run rustlings dev update to update it”——所以新增练习后忘记执行 dev update 是 PR 被 CI 打回的一个常见原因。
第五步:本地跑通 dev check,再开 PR
提交 PR 前,在仓库根目录执行:
rustlings dev check --require-solutions
这正是 CI 工作流 中 dev-check job 运行的命令(cargo dev check --require-solutions)。本地通过,PR 的准入检查基本稳了。
校验闸门:dev check 在背后强制了什么
理解 dev check 检查了什么,等于理解了一道新练习要满足的全部硬性标准。以下规则全部来自 src/dev/check.rs 的源码:
元数据层面
- 练习总数不得超过 999(
MAX_N_EXERCISES); name长度不超过 32 字符,且只允许字母、数字和下划线(forbidden_char函数对name与dir同样生效);name全局唯一,重复即报错;hint不允许为空——即使是“不需要提示”,也要用文字说明为什么不需要;info.toml的format_version必须与程序当前支持的版本相等:偏小要求迁移格式,偏大则提示升级 Rustlings 程序。
练习文件层面
- 每个练习文件必须包含
fn main()(哪怕是空函数),以避免语言服务器报错; - 必须至少有一处
// TODO注释; #[test]的存在与否必须与test字段声明一致;exercises/目录(含一层子目录)只允许出现README.md和info.toml中登记的.rs练习文件,不允许更深层嵌套——任何游离文件都会被check_unexpected_files揪出来。
运行行为层面
- 并行运行所有练习(受
skip_check_unsolved豁免的除外),任何一个已经处于“已解决”状态就报错,防止把答案泄漏进练习; - 并行运行所有答案文件,答案必须编译、测试、Clippy 与运行全部通过;
- 对通过的答案执行
rustfmt --check --edition 2024,答案必须已格式化; --require-solutions开启时,缺少答案文件直接判失败(官方仓库 CI 正是以该模式运行,因此官方练习的答案是必配项)。
练习与答案的“通过”定义来自 src/exercise.rs 的 RunnableExercise::run:依次执行 cargo build →(若 test 为真)cargo test → cargo clippy --profile test(strict_clippy 为真时追加 -D warnings)→ 运行二进制并要求退出码为 0。--profile test 保证 #[cfg(test)] 代码也被 Clippy 检查——这也解释了为什么练习里写测试代码同样要满足 Clippy 标准。
仓库内还有一组用于验证这套检查体系自身的测试工程:tests/test_exercises/ 下刻意准备了 compilation_failure.rs、compilation_success.rs、test_failure.rs、test_success.rs 和未登记的 not_in_info.rs 五类样本,供集成测试(tests/integration_tests.rs)断言各类失败模式能被正确识别。
CI 准入清单:你的 PR 会经过哪些检查
汇总 .github/workflows/rust.yml 的定义,一个涉及仓库代码的 push/PR 会触发以下作业:
- clippy:
cargo clippy -- --deny warnings(任何警告都是失败); - fmt:
cargo fmt --all --check; - test:
cargo test --workspace,在 ubuntu/windows/macos 三平台矩阵上运行; - dev-check:
cargo dev check --require-solutions,即上文完整拆解的那套练习校验; - rumdl:Markdown 风格检查。
值得注意的是该工作流的 paths-ignore 配置:*.md 文件、website/ 目录的改动不会触发这条流水线。所以仅修订 CONTRIBUTING.md 这类文档本身只经过最轻量的检查,而任何涉及练习、源码或 Cargo.toml 的改动都会完整走一遍上述闸门。
贡献前自查清单
综合 CONTRIBUTING.md 与源码校验逻辑,新增一道练习前可对照以下清单:
- 练习文件
exercises/<topic>/<topicN>.rs已创建,含fn main()与至少一处// TODO; - 目录
README.md已补充主题说明与 The Book 章节链接; - 答案
solutions/<topic>/<topicN>.rs已编写、含解释性注释,且已rustfmt格式化; info.toml已按name/dir/hint注册练习(官方练习写入 rustlings-macros/info.toml,社区练习仓库写入根目录info.toml);无测试的练习已显式声明test = false;- 已运行
rustlings dev update刷新Cargo.toml的 bin 列表; rustlings dev check --require-solutions本地全部通过;- 非琐碎改动已先开 Issue 讨论;LLM 参与生成的代码部分已在 PR 中如实披露;
- 若同时修改了现有练习,其对应答案已同步更新。
这套“文档约定 + 自动校验”的双层结构,是 Rustlings 在保持低贡献门槛(fork 即可提交)的同时,让 94 道官方练习长期保持一致格式与行为的关键机制。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00