How Rustlings Tracks Exercise Progress: A Deep Dive into the State Management System
Rustlings tracks exercise progress through a lightweight .rustlings-state.txt file that persists completed exercises and the current position, which is loaded into an AppState struct at runtime to drive the CLI workflow and progress visualization.
The rust-lang/rustlings repository provides a hands-on environment for learning Rust through incremental exercises. To maintain continuity across sessions, Rustlings implements a file-based state management system that records which exercises have been completed and determines the next logical step in the curriculum. Understanding how Rustlings tracks exercise progress reveals an elegant, minimal approach to persistent state in CLI applications.
The State File Architecture
Rustlings persists progress in a simple text file named .rustlings-state.txt located in the current working directory. This file stores two critical pieces of information: the name of the current exercise the user is working on, and a newline-separated list of exercises marked as completed.
The file format is intentionally minimal. It contains a header followed by the current exercise name on its own line, then a blank line, followed by each completed exercise name on separate lines. This simplicity allows for easy debugging and manual recovery if needed.
Loading and Parsing Persisted State
When Rustlings starts, the AppState::new method in src/app_state.rs attempts to open or create .rustlings-state.txt. If the file exists, Rustlings parses it to reconstruct the user's session.
The parsing logic skips the header and extracts the current exercise name, then iterates through the remaining lines to build a set of completed exercises:
// src/app_state.rs
let mut lines = file_buf.split(|c| *c == b'\n').skip(2);
let Some(current_exercise_name) = lines.next() else { … };
…
for done_exercise_name in lines {
if !done_exercise_name.is_empty() {
done_exercises.insert(done_exercise_name);
}
}
For each completed exercise found in the state file, Rustlings sets the corresponding Exercise.done flag to true and increments the internal n_done counter. This restores the exact progress state from the previous session.
Runtime State Management
During an active session, Rustlings maintains exercise status in memory using the Exercise struct defined in src/exercise.rs. Each exercise carries a done boolean field that reflects whether the user has successfully completed it.
The AppState::set_status method in src/app_state.rs handles state transitions:
// src/app_state.rs
if exercise.done == done {
return Ok(false);
}
exercise.done = done;
if done { self.n_done += 1; } else { self.n_done -= 1; }
This method returns true only when the state actually changes, allowing callers to determine whether the state file needs to be rewritten. This optimization prevents unnecessary disk writes when the status remains unchanged.
Persisting Progress Updates
When progress changes must survive across program restarts, Rustlings calls AppState::write to atomically update .rustlings-state.txt. This method reconstructs the file buffer from the current in-memory state.
The write operation appends the current exercise name followed by a newline, then iterates through all exercises to append the names of those marked as done:
// src/app_state.rs
self.file_buf.truncate(STATE_FILE_HEADER.len());
self.file_buf.extend_from_slice(self.current_exercise().name.as_bytes());
self.file_buf.push(b'\n');
for exercise in &self.exercises {
if exercise.done {
self.file_buf.push(b'\n');
self.file_buf.extend_from_slice(exercise.name.as_bytes());
}
}
The buffer is then written atomically to .rustlings-state.txt, ensuring that the state file always contains a complete, consistent snapshot of the user's progress.
Visualizing Progress in the Terminal
Rustlings provides real-time visual feedback through the CheckProgressVisualizer in src/term.rs. During bulk operations like rustlings check, the system spawns a thread pool to validate exercises concurrently and communicates status updates through a channel.
The visualizer receives CheckProgress enum values (None, Checking, Done, Pending) for each exercise and renders a dynamic progress bar:
// src/term.rs (simplified)
pub fn progress_bar<'a>(progress: u16, total: u16) -> impl Write { … }
This displays the completion ratio (e.g., 3/12) with a filled bar, giving users immediate visibility into their overall progress through the Rustlings curriculum.
Navigation and Workflow Control
The AppState struct provides utilities to navigate between exercises based on completion status. The next_pending_exercise_ind method scans the exercises vector to find the next incomplete exercise, starting from the current position and wrapping around to the beginning if necessary. It returns None only when all exercises are completed.
For workflow management, the reset_current_exercise method (invoked by rustlings reset) clears the current exercise's done flag and restores the original source code. This allows users to retry exercises without manually editing files or corrupting their progress history.
Key Source Files and Implementation Details
Understanding how Rustlings tracks exercise progress requires familiarity with these core files:
| File | Role |
|---|---|
[src/app_state.rs](https://github.com/rust-lang/rustlings/blob/main/src/app_state.rs) |
Central state manager; reads/writes .rustlings-state.txt, tracks Exercise.done, provides navigation helpers. |
[src/info_file.rs](https://github.com/rust-lang/rustlings/blob/main/src/info_file.rs) |
Deserialises info.toml (list of all exercises) and defines ExerciseInfo. |
[src/term.rs](https://github.com/rust-lang/rustlings/blob/main/src/term.rs) |
Implements terminal utilities, notably progress_bar and CheckProgressVisualizer. |
[src/watch/state.rs](https://github.com/rust-lang/rustlings/blob/main/src/watch/state.rs) |
Holds runtime state for rustlings watch mode, re‑using the same CheckProgress enum. |
[src/exercise.rs](https://github.com/rust-lang/rustlings/blob/main/src/exercise.rs) |
Defines the Exercise struct used throughout the progress logic. |
These files collectively give Rustlings a minimal yet reliable mechanism for persisting and visualising user progress across runs.
Practical Code Examples
Loading State and Listing Pending Exercises
To programmatically access the progress state, load the AppState and filter for incomplete exercises:
use rustlings::{AppState, info_file::InfoFile};
fn pending_exercises() -> anyhow::Result<Vec<String>> {
// Parse the static info (list of all exercises)
let info = InfoFile::parse()?;
// Initialise AppState, which automatically reads .rustlings-state.txt
let (mut app_state, _) = AppState::new(info.exercises, "")?;
// Collect names of exercises that are *not* done
let pending = app_state
.exercises()
.iter()
.filter(|ex| !ex.done)
.map(|ex| ex.name.to_string())
.collect();
Ok(pending)
}
Marking the Current Exercise as Done
When implementing custom workflow logic, use done_current_exercise to update progress:
fn mark_current_done(app_state: &mut AppState, stdout: &mut std::io::StdoutLock) -> anyhow::Result<()> {
// `done_current_exercise` updates the flag, writes the state file,
// and moves to the next pending exercise (or finishes).
app_state.done_current_exercise::<true>(stdout)?;
Ok(())
}
Rendering a Progress Bar Manually
For custom tooling that reports progress, leverage the terminal utilities:
use rustlings::term::{self, CountedWrite};
fn demo_progress(total: u16) {
let mut out = std::io::stdout();
for i in 0..=total {
term::progress_bar(i, total)
.counted_write(&mut out)
.expect("failed to draw bar");
std::thread::sleep(std::time::Duration::from_millis(150));
}
}
Summary
- Rustlings persists progress in a plaintext file named
.rustlings-state.txtstored in the working directory. - The
AppStatestruct insrc/app_state.rsmanages the lifecycle of this file, parsing it on startup and rewriting it atomically when progress changes. - Each exercise carries a
doneboolean flag that determines navigation flow and drives visual progress indicators. - Real-time progress visualization uses
CheckProgressVisualizerinsrc/term.rsto render terminal bars during bulk check operations. - The system supports workflow commands like
rustlings resetby clearing completion flags and restoring original exercise files without corrupting progress history.
Frequently Asked Questions
Where does Rustlings store my progress?
Rustlings stores your progress in a file named .rustlings-state.txt located in the current working directory where you execute the rustlings command. This file contains the name of your current exercise and a newline-separated list of all exercises you have completed. The simple text format allows for easy manual inspection or recovery if the file becomes corrupted.
Can I manually edit the Rustlings state file?
Yes, you can manually edit .rustlings-state.txt because it uses a straightforward text format with a header, the current exercise name, and a list of completed exercises separated by newlines. However, manual editing is generally unnecessary since the rustlings reset command provides a safe mechanism to modify exercise status programmatically. If you do edit the file manually while Rustlings is running, you should restart the application to ensure it loads the updated state.
How does Rustlings determine which exercise to show next?
Rustlings uses the next_pending_exercise_ind method in src/app_state.rs to find the next incomplete exercise. This method scans the internal exercises vector starting from the current position, looking for the first exercise where the done flag is false. If it reaches the end without finding a pending exercise, it wraps around to the beginning of the list. The method returns None only when all exercises in the curriculum are marked as completed.
What happens to my progress if I delete the state file?
If you delete .rustlings-state.txt, Rustlings will treat the next execution as a fresh start. The AppState::new method will create a new state file and initialize all exercises as pending (not done), effectively resetting your progress to zero. Your exercise source code files will remain unchanged unless you manually edit them or use the rustlings reset command to restore them to their original state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →