Lab: build a Rust notebook
The most efficient way to learn Rust as an orchestrator is to direct your agent through a small, real project. This lab is the project. You drive, the agent codes, the cargo gates keep both of you honest.
The project: a tiny notebook CLI. Five versions, each version teaching one bucket of concepts. By V5, you have a working rust-notebook binary that takes commands, persists to JSON, and exports HTML.
Setup
cargo new rust-notebook --bin
cd rust-notebookOpen the repo in your editor. Drop in a .gitignore (Cargo's cargo new already adds one). Add the cargo gates pre-commit hook from the cargo gates chapter.
V1: in-memory notes
Concepts: structs, vectors, loops, formatting.
Goal: A program that creates 3 sample notes in memory and prints them.Prompt the agent:
"In
src/main.rs, define a structNote { id: u64, title: String, body: String }. Inmain, create three sample notes in aVec<Note>, then print them. DeriveDebugforNoteso you can use{:?}. Runcargo runto verify."
After the diff: confirm Note is a public-ish struct with simple fields, Vec<Note> is local to main, and the print loop uses for note in ¬es (borrow, not move).
V2: CLI arguments
Concepts: enums, match, argument parsing.
Goal: rust-notebook add "Title" "Body" / list / search "query"Prompt:
"Add
clap(derive feature) as a dependency. Define aClistruct and aCommandenum withAdd { title, body },List,Search { query }variants. Parse args, match on the command, and route to a function per variant. Move the sample notes into afn sample_notes() -> Vec<Note>. ForSearch, do case-insensitive substring matching on title and body."
After the diff: enum has three variants (one each style: struct, unit, struct). match is exhaustive. No unwrap() on the parse.
V3: file storage
Concepts: Result, ?, file IO, serialization, error types.
Goal: Notes persist to ~/.rust-notebook/notes.json across runs.Prompt:
"Add
serde(derive feature) andserde_jsonanddirs. DeriveSerialize, DeserializeonNote. Define aNotebookstruct that ownsVec<Note>and methodsload(path) -> Result<Self, NotebookError>,save(&self, path) -> Result<(), NotebookError>,add,list,search. DefineNotebookErroras athiserror::Errorenum with variantsIo(#[from] std::io::Error),Json(#[from] serde_json::Error). Wire upmainso commands load, mutate, save. Usedirs::config_dir()to find the storage path."
After the diff: error enum is concrete (no Box<dyn Error>, no anyhow in the library code). ? does the propagation. Zero unwrap() outside of main if at all.
V4: HTML export
Concepts: strings, file write, modules, templates, escaping.
Goal: rust-notebook export ~/notebook.html — a beautiful single-file HTML.Prompt:
"Add an
Exportcommand to the CLI:Export { output: PathBuf }. Create a new modulesrc/export.rswithpub fn render(notes: &[Note]) -> String. Build the HTML as a string: include<style>for a dark theme with rust orange accents, output each note as a card with title and body. Escape any HTML-unsafe characters in note content (use thehtml-escapecrate or write a tiny escape function). Runcargo clippy -- -D warningsand fix anything it flags."
After the diff: a new module, the export function takes a borrow not a move, HTML escaping actually happens. Bonus: the agent might split rendering into helpers.
V5: tests and refactor
Concepts: unit tests, integration tests, lib.rs, error-path coverage.
Goal: Move core logic to a library, test it, harden it.Prompt:
"Convert this into a library + binary. Move
Note,Notebook,NotebookError, andexport::renderintosrc/lib.rsand submodules. Keep only argument parsing and dispatch insrc/main.rs. Add unit tests for: adding a note, listing, searching (case-insensitive, substring), error path for missing file (returnsOkwith empty notebook), error path for corrupted JSON (returnsNotebookError::Json). Add an integration test intests/cli.rsthat usesassert_cmdto invoke the binary and check output. Runcargo fmt && cargo clippy -- -D warnings && cargo testand fix everything."
After the diff: lib.rs exists, the binary is thin. Every public function has a test. Each Err variant has a test that produces it.
What you have at V5
A real, small Rust binary that does something useful and demonstrates:
- Structs, enums,
match. Resultandthiserror.- File IO, serialization with
serde. - Modules and library structure.
- Unit and integration tests.
- Cargo gates green.
The skill you have at V5 is not "I can write this from scratch." It is "I can direct an agent to build this end-to-end, review what it writes, and catch the failure modes." That is the orchestrator skill the whole site is about.
Going further
If V5 felt easy, add one of these:
- V6: search ranking. TF-IDF or BM25 across titles and bodies. Adds an algorithm and tests for correctness, not just behavior.
- V7: full-text indexing. A persistent inverted index in a separate file, rebuilt incrementally.
- V8: HTTP server. Wrap the same library in
axum. Now the same logic powers a CLI and an API. - V9: async file IO + watch mode.
notifycrate to live-refresh exports when a note changes. - V10: Tantivy or Lance. Integrate a real search/vector engine. Now you are touching the agent infra layer.
Pick what teaches you the bucket of Rust you are weakest on. The agent is willing.