Thanks for your interest in improving the Rust State Machine tutorial. This guide covers the structure of the project and the rules you should follow when making changes.
Steps live in src/ and are numbered sequentially. Each step is one of three types:
Section headers introduce a new section of the tutorial. They contain only a README.md with no code.
Action steps have a source/ directory with the project state after the action and a README.md explaining what to do. No template/solution split.
Template/solution steps are the most common. They look like this (e.g., src/8/):
src/N/
├── README.md # Wrapper that includes template/README.md
├── files.json # Manifest of files shown in the editor
├── template/ # Starting code with TODO comments
│ └── src/
└── solution/ # Completed code
└── src/
The most important property of this tutorial is that each step's template must match the previous step's solution exactly, except for the new TODO comments and scaffolding introduced by the current step.
This means a student finishing step N and moving to step N+1 should see only the new instructions, not unexplained changes to existing code.
Any difference between step N's solution and step N+1's template that is not explained by step N+1's README is a bug. Common examples:
- Changing code style without mentioning it (e.g., switching from
///doc comments to//regular comments) - Reformatting a function from single-line to multi-line (or vice versa) when the step doesn't touch that function
- Changing a
matcharm from direct return style to?/Ok(())style without explanation - Fixing typos in code from earlier steps (fix them in the step where they were introduced instead)
Diff the previous solution against the current template:
diff -r src/N/solution/src/ src/N+1/template/src/Every difference you see should be either a TODO comment or scaffolding that the README explains. If you see anything else, it needs to be fixed.
- Use
/* TODO: ... */comments to tell the student what to write. - TODO text should use the same type names, function names, and casing that the solution uses. If the solution uses
AccountId, don't writeAccountIDin the TODO. - Keep TODOs concise but specific enough that the student knows what to do without guessing.
- Every solution must compile, pass clippy, and pass tests. Run
bash check.sh checkto verify. - The
solution/README.mdshould contain exactly:
# Step Title
You can find the solution to the previous step here.Use "to" (not "for"), and always include "You can find" at the start.
Each step with code has a files.json that tells the editor which files to display:
{
"template": [
{ "label": "src/balances.rs", "path": "./template/src/balances.rs", "status": "M" }
],
"solution": [
{ "label": "src/balances.rs", "path": "./solution/src/balances.rs", "status": "M" }
]
}statusis"A"for files added in this step and"M"for files modified from the previous step.- Only list files that actually change in this step. If a file is identical between this template and the previous solution, don't include it.
- Labels should use the path relative to the crate root (e.g.,
src/balances.rs, notbalances.rs).
All code must pass cargo +nightly fmt. The project uses hard_tabs = true (see rustfmt.toml in each step).
Run the formatter on all steps:
bash check.sh fixAll steps must pass clippy with warnings denied, but there are intentional exceptions:
-
unusedwarnings are suppressed in all steps. Templates contain TODO stubs andunimplemented!()calls that naturally produce unused imports, variables, and dead code warnings. These are expected and would be distracting to students, socheck.shpasses-A unusedto all steps. -
clippy::ptr-argis allowed in steps 1-26. Early steps use&Stringparameters (e.g.,fn balance(&self, who: &String)) because the tutorial hasn't introduced generics yet. Clippy would normally suggest&strinstead, but that change happens naturally in steps 18-22 when the code is refactored to use generic types. Allowingptr-argin early steps keeps the focus on the concepts being taught rather than premature optimization. -
Steps 27+ enforce full clippy strictness (minus
unused). By this point the code uses generics and theptr-arglint is no longer relevant.
- Use
///doc comments for public functions and structs that students write. - Use
//regular comments for implementation notes and explanations. - Be consistent across steps. If step 5 introduces
/// Create a new instance of the balances module.on a function, every later step should keep it as///, not silently change it to//.
bash check.sh checkThis runs cargo +nightly fmt --check, cargo +nightly clippy, and cargo test on every template and solution directory in parallel.
bash check.sh fixcd src/8/solution
cargo +nightly fmt --check
cargo +nightly clippy -- -D warnings
cargo testThe tutorial is published as an mdBook. The configuration is in book.toml and the table of contents is src/SUMMARY.md.
To build and preview locally:
mdbook serveThe step wrapper READMEs use {{#include ./template/README.md}} to pull in content, so editing the inner README is what matters. The outer src/N/README.md files are boilerplate wrappers that set up the interactive Monaco editor and should not need changes unless the editor UI itself is being updated.
If you add or remove a step, update src/SUMMARY.md to match.
- Make your changes on the
masterbranch. - Run
bash check.sh checkand confirm everything passes. - For any step you changed, diff its template against the previous step's solution to verify the minimal diff rule.
- Open a PR with a clear description of what you changed and why.
If your change affects multiple steps (e.g., renaming a type that carries forward), make sure you update every step from the point of introduction onward, and verify the solution-to-template chain is clean across all of them.