# How Rustlings Tracks Exercise Progress: A Deep Dive into the State Management System

> Discover how Rustlings tracks exercise progress using its state management system. Learn about the .rustlings-state.txt file and AppState struct that powers the CLI.

- Repository: [The Rust Programming Language/rustlings](https://github.com/rust-lang/rustlings)
- Tags: internals
- Published: 2026-03-05

---

**Rustlings tracks exercise progress through a lightweight [`.rustlings-state.txt`](https://github.com/rust-lang/rustlings/blob/main/.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`](https://github.com/rust-lang/rustlings/blob/main/.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`](https://github.com/rust-lang/rustlings/blob/main/src/app_state.rs) attempts to open or create [`.rustlings-state.txt`](https://github.com/rust-lang/rustlings/blob/main/.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:

```rust
// 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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/src/app_state.rs) handles state transitions:

```rust
// 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`](https://github.com/rust-lang/rustlings/blob/main/.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:

```rust
// 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`](https://github.com/rust-lang/rustlings/blob/main/.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`](https://github.com/rust-lang/rustlings/blob/main/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:

```rust
// 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)](https://github.com/rust-lang/rustlings/blob/main/src/app_state.rs) | Central state manager; reads/writes [`.rustlings-state.txt`](https://github.com/rust-lang/rustlings/blob/main/.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)](https://github.com/rust-lang/rustlings/blob/main/src/info_file.rs) | Deserialises [`info.toml`](https://github.com/rust-lang/rustlings/blob/main/info.toml) (list of all exercises) and defines `ExerciseInfo`. |
| [[`src/term.rs`](https://github.com/rust-lang/rustlings/blob/main/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)](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)](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:

```rust
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:

```rust
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:

```rust
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.txt`](https://github.com/rust-lang/rustlings/blob/main/.rustlings-state.txt) stored in the working directory.
- The `AppState` struct in [`src/app_state.rs`](https://github.com/rust-lang/rustlings/blob/main/src/app_state.rs) manages the lifecycle of this file, parsing it on startup and rewriting it atomically when progress changes.
- Each exercise carries a `done` boolean flag that determines navigation flow and drives visual progress indicators.
- Real-time progress visualization uses `CheckProgressVisualizer` in [`src/term.rs`](https://github.com/rust-lang/rustlings/blob/main/src/term.rs) to render terminal bars during bulk check operations.
- The system supports workflow commands like `rustlings reset` by 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`](https://github.com/rust-lang/rustlings/blob/main/.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`](https://github.com/rust-lang/rustlings/blob/main/.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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/.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.