How Rustlings Parses Exercise Metadata from `info.toml`

Rustlings reads the info.toml file either from the local directory for community exercises or from an embedded byte slice for official exercises, then deserializes it using serde and the toml crate into a strongly-typed Info struct, validating fields like name and directory before use.

Rustlings, the popular interactive Rust tutorial, stores exercise configurations in a TOML file named info.toml. Understanding how Rustlings parses exercise metadata from info.toml reveals how the tool dynamically loads official and community-contributed lessons while ensuring data integrity through strict validation.

Locating the info.toml Source

Rustlings supports two distinct sources for exercise metadata: a local file for community-driven content and an embedded resource for the official curriculum shipped with the binary.

Community vs. Official Exercises

When the CLI starts, it first checks whether a file named info.toml exists in the current working directory. If present, Rustlings treats this as a community exercise manifest and reads it directly from disk using fs::read("info.toml"). This behavior is implemented in src/info_file.rs around lines 96‑103, where the code branches based on the existence of the local file.

If no local info.toml is found, the application falls back to the official exercise set. The official metadata is embedded into the binary at compile time via the rustlings-macros crate. The macro generates a constant EMBEDDED_INFO_TOML that contains the raw bytes of the bundled info.toml file (see rustlings-macros/src/lib.rs lines 21‑29). The runtime code in src/info_file.rs simply converts this byte slice to a vector and proceeds with parsing.

Embedded File Handling

The embedded file path is defined in rustlings-macros/info.toml, which serves as the single source of truth for official exercise metadata. During the build process, the macro reads this file and emits a const EMBEDDED_INFO_TOML: &[u8] = include_bytes!(...); declaration. At runtime, src/info_file.rs accesses this constant when the local file is absent, ensuring the binary is self‑contained and does not require external data files.

Deserializing TOML into Rust Structs

Once the raw bytes are obtained—whether from disk or the embedded constant—the next step is to transform the TOML text into a structured Rust representation.

The Info Struct Definition

The root data structure is named Info and is defined in src/info_file.rs. It derives serde::Deserialize to enable automatic mapping from TOML keys to struct fields. The struct captures metadata such as:

  • name – the exercise identifier (e.g., "variables1")
  • dir – the directory containing the exercise files
  • path – the relative path to the source file
  • test – a boolean indicating whether the exercise includes tests
  • hint – an optional string providing guidance to the learner

Using serde and toml Crates

The deserialization is performed by the toml crate’s from_str function, which relies on serde for the heavy lifting. The relevant code in src/info_file.rs (around line 106) looks like this:

let info: Info = toml::from_str(&content)
    .context("Failed to parse the `info.toml` file")?;

Here, content is the UTF‑8 string obtained earlier. The context method (from the anyhow crate) attaches a helpful error message if the TOML is malformed or contains invalid syntax.

Validating Parsed Metadata

Parsing alone is not sufficient; Rustlings enforces several invariants to prevent runtime errors or confusing user experiences.

Field Presence Checks

Immediately after deserialization, the code validates that critical fields are non‑empty. For example, in src/info_file.rs around line 68, the application checks the exercise name:

if info.name.is_empty() {
    bail!("Found an empty exercise name in `info.toml`");
}

Similarly, the dir field is validated around line 81:

if info.dir.is_empty() {
    bail!("The exercise `{name}` has an empty dir name in `info.toml`");
}

These checks use the bail! macro from anyhow to return a descriptive error early, preventing the CLI from attempting to access non‑existent directories.

Test Flag Consistency

The test boolean determines whether Rustlings should compile and run unit tests for the exercise. In src/dev/check.rs (lines 125‑130), the code verifies that when test is false, the corresponding Rust source file does not contain #[test] functions. This ensures the metadata accurately reflects the exercise structure and avoids misleading the learner about whether tests are expected.

Code Examples

Below are simplified, runnable illustrations of the parsing logic used by Rustlings.

Loading the metadata

use std::fs;
use anyhow::{Context, Result};

fn load_info() -> Result<Info> {
    // 1️⃣ Prefer a local `info.toml` for community exercises.
    let raw = if fs::metadata("info.toml").is_ok() {
        fs::read("info.toml")
            .context("Failed to read the `info.toml` file")?
    } else {
        // 2️⃣ Fall back to the embedded file for official exercises.
        EMBEDDED_INFO_TOML.to_vec()
    };

    // 3️⃣ Convert to UTF-8.
    let content = String::from_utf8(raw)
        .context("Failed to parse `info.toml` as UTF8")?;

    // 4️⃣ Deserialize Toml into `Info`.
    toml::from_str(&content)
        .context("Failed to parse the `info.toml` file")
}

Using the parsed data

fn run_exercise() -> Result<()> {
    let info = load_info()?;
    println!("Running exercise: {}", info.name);
    if info.test {
        // compile and run the associated tests
    } else {
        // just compile the source file
    }
    Ok(())
}

Summary

  • Rustlings locates info.toml either in the current directory (community exercises) or falls back to an embedded byte slice compiled into the binary (official exercises).
  • The file contents are read into a UTF‑8 string and deserialized into a strongly‑typed Info struct using serde and the toml crate.
  • Immediate validation ensures required fields like name and dir are non‑empty, preventing runtime errors when accessing exercise files.
  • The test flag is cross‑checked against the actual source code to guarantee metadata consistency.
  • All parsing logic resides in src/info_file.rs, while the embedded data is generated by rustlings-macros/src/lib.rs and validated in src/dev/check.rs.

Frequently Asked Questions

How does Rustlings decide whether to use a local info.toml or the embedded one?

When the CLI starts, it checks the current working directory for a file named info.toml. If the file exists, Rustlings treats it as a community exercise manifest and reads it directly from disk using fs::read. If the file is absent, the application falls back to the constant EMBEDDED_INFO_TOML, which is generated at compile time by the rustlings-macros crate and contains the official exercise metadata bundled into the binary.

What Rust crates does Rustlings use to parse the TOML file?

Rustlings relies on two core crates for metadata parsing: serde for deriving deserialization traits and toml for the actual parsing logic. The Info struct derives Deserialize, allowing toml::from_str to automatically map TOML keys to struct fields. Error handling is enhanced with the anyhow crate, which provides the Context trait and bail! macro for attaching descriptive messages to parsing failures.

What validation does Rustlings perform after parsing info.toml?

After deserialization, Rustlings validates that critical fields are populated. It checks that the exercise name is not empty and that the dir field contains a valid directory name, returning early with a descriptive error if either check fails. Additionally, the tool verifies that the test boolean accurately reflects the presence of #[test] functions in the exercise source file, ensuring the metadata does not mislead the learner about whether tests are expected.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →