# How Rustlings Parses Exercise Metadata from `info.toml`

> Discover how Rustlings parses exercise metadata from info.toml using serde and the toml crate. Learn about struct deserialization and field validation for efficient exercise management.

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

---

**Rustlings reads the [`info.toml`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/info.toml). Understanding how Rustlings parses exercise metadata from [`info.toml`](https://github.com/rust-lang/rustlings/blob/main/info.toml) reveals how the tool dynamically loads official and community-contributed lessons while ensuring data integrity through strict validation.

## Locating the [`info.toml`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/info.toml) file (see [`rustlings-macros/src/lib.rs`](https://github.com/rust-lang/rustlings/blob/main/rustlings-macros/src/lib.rs) lines 21‑29). The runtime code in [`src/info_file.rs`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/src/info_file.rs) (around line 106) looks like this:

```rust
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`](https://github.com/rust-lang/rustlings/blob/main/src/info_file.rs) around line 68, the application checks the exercise name:

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

```

Similarly, the `dir` field is validated around line 81:

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

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

```rust
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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/src/info_file.rs), while the embedded data is generated by [`rustlings-macros/src/lib.rs`](https://github.com/rust-lang/rustlings/blob/main/rustlings-macros/src/lib.rs) and validated in [`src/dev/check.rs`](https://github.com/rust-lang/rustlings/blob/main/src/dev/check.rs).

## Frequently Asked Questions

### How does Rustlings decide whether to use a local [`info.toml`](https://github.com/rust-lang/rustlings/blob/main/info.toml) or the embedded one?

When the CLI starts, it checks the current working directory for a file named [`info.toml`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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.