Back to Articles

Aquascope: Teaching Rust's Ownership Model Through Compiler-Backed Visualizations

[ View on GitHub ]

Aquascope: Teaching Rust’s Ownership Model Through Compiler-Backed Visualizations

Hook

Most Rust learners struggle with ownership not because the concept is inherently difficult, but because the compiler’s reasoning happens in an invisible dimension—Aquascope makes that dimension visible by intercepting the borrow checker’s decisions and rendering them as interactive diagrams.

Context

Teaching Rust’s ownership system has always been a pedagogical challenge. The borrow checker operates on lifetimes and permissions that exist only at compile-time, making abstract concepts like “this reference borrows this value mutably for this scope” difficult to visualize. Traditional teaching materials rely on manual diagrams or static illustrations that quickly become outdated or inaccurate as code evolves. Worse, these diagrams often represent the author’s mental model rather than the compiler’s actual analysis, creating subtle mismatches between explanation and reality.

The Cognitive Engineering Lab at Brown and Stanford developed Aquascope as research software to address this gap, publishing their pedagogical framework at OOPSLA 2023. Rather than asking educators to manually draw ownership diagrams, Aquascope extracts the ground truth directly from rustc’s internal data structures—the same MIR and borrow checker facts that determine whether code compiles. The result is a toolchain that embeds interactive visualizations into mdBook documentation, showing precisely which variables own data, where borrows occur, and how permissions flow through a program’s execution.

Technical Insight

Code blocks with

aquascope attributes

Invokes per block

Loads plugin

Hooks into

MIR + borrow facts

Queries

Runtime execution trace

Serializes

Analysis metadata

Renders

Embedded in

Rust Source Code

with Annotations

mdBook Preprocessor

mdbook-aquascope

cargo-aquascope

Build Orchestrator

aquascope_front

Compiler Plugin

rustc Nightly

Borrow Checker + MIR

Miri Interpreter

Runtime Trace

Analysis JSON

Ownership + Lifetime Data

Frontend Engine

Depot + JS

Interactive SVG

Visualization

System architecture — auto-generated

Aquascope’s architecture splits into three coordinated components: a compiler plugin that extracts analysis data, a runtime interpreter integration, and a frontend visualization engine. The compiler plugin (aquascope_front) registers custom callbacks with rustc to intercept the borrow checker’s intermediate results after type-checking completes. It queries the compiler’s MIR (Mid-level Intermediate Representation) to track ownership transfers, borrow creation points, and lifetime regions, serializing this information into JSON metadata.

Integrating Aquascope into educational content happens through mdBook preprocessing. You annotate code blocks with special attributes that specify visualization boundaries:

#[aquascope::boundaries]
fn example() {
    let mut data = vec![1, 2, 3];
    let r1 = &data;
    println!("Length: {}", r1.len());
    let r2 = &mut data; // Shows permission change
    r2.push(4);
}

The aquascope::boundaries attribute tells the compiler plugin where to capture ownership state. When mdbook-aquascope preprocesses your documentation, it invokes cargo-aquascope on each annotated block, which compiles the snippet with the custom compiler plugin loaded. The plugin walks the MIR basic blocks, correlating source spans with borrow checker facts—for each program point, it records which variables are live, what permissions they hold (read, write, own), and which references alias which memory locations.

For runtime behavior visualization, Aquascope leverages Miri, Rust’s interpreter that can execute MIR directly. While the borrow checker analysis shows compile-time permissions, Miri traces actual execution to visualize stack frames, heap allocations, and pointer relationships. This dual approach means Aquascope can show both “the compiler knows this borrow is valid here” and “at runtime, this pointer addresses this heap allocation.”

The frontend receives this analysis data as JSON and renders it using a custom JavaScript visualization library. The rendering engine generates interactive SVG diagrams with three primary views: permissions timelines showing how read/write/own capabilities change across program points, memory diagrams depicting stack and heap layout with pointer connections, and step-through execution that lets learners advance through code line-by-line watching ownership evolve. These aren’t static images—clicking a variable highlights all its borrows, hovering over a scope shows its lifetime extent, and timeline scrubbing reveals how permissions propagate through function calls.

The technical complexity shows in the installation requirements. Aquascope pins to specific nightly toolchains (currently nightly-2024-12-15) because it depends on unstable compiler internals that change frequently. You need rustc-dev and llvm-tools-preview installed for that exact toolchain, Miri configured properly, and the Depot build tool for frontend assets. The mdbook-aquascope preprocessor then orchestrates all these components during documentation builds.

One particularly sophisticated aspect is how Aquascope handles lifetime regions. Rust’s lifetimes are compile-time constructs with complex subtyping relationships—the compiler reasons about variance, outlives constraints, and higher-ranked trait bounds. Aquascope doesn’t simplify this complexity; instead, it visualizes the actual regions computed by the borrow checker, including their hierarchical relationships. When a function signature has 'a: 'b constraints, the diagram shows the containment relationship between those lifetime scopes.

Gotcha

Aquascope’s research software status is not a disclaimer—it’s a design constraint you must plan around. The tool breaks with every rustc nightly update because it relies on compiler internals that have no stability guarantees. The exact nightly version is hardcoded in the repository, and upgrading requires someone to update the compiler plugin’s queries and data structure accesses to match rustc’s latest APIs. If you’re building a course or book with Aquascope visualizations, you’re committing to maintaining that integration as the tool evolves. The documentation explicitly warns that syntax and configuration are unstable, meaning your mdBook annotations might need updates even between minor releases.

The integration complexity extends to the build process. You can’t just cargo install aquascope and start visualizing—you need the entire toolchain stack (specific nightly, compiler components, Miri, Depot) configured correctly. The frontend build uses Depot, a custom JavaScript tool from the same lab, rather than standard npm/webpack workflows. This makes sense for the project’s research context but adds friction for educators who just want to add diagrams to documentation. Additionally, Aquascope is tightly coupled to mdBook; there’s no standalone CLI that analyzes arbitrary Rust files and outputs visualizations. You’re adopting both the tool and its intended workflow.

Performance and scalability aren’t design priorities. The analysis runs full compiler passes plus Miri interpretation for every annotated code block in your documentation. Large books with many examples will see significant build time increases. The visualizations also load JavaScript bundles for interactive rendering, affecting page weight and initial load times for learners on slower connections.

Verdict

Use Aquascope if: you’re creating educational Rust content (books, courses, workshops) where accurate ownership visualization directly serves learning outcomes, you can commit to maintaining builds against specific toolchain versions, you’re already using mdBook for documentation, and you value pedagogical precision over tooling convenience. The research-backed visualizations provide unmatched accuracy because they show the compiler’s actual reasoning, not approximations. Skip if: you need production-stable tooling that won’t break with compiler updates, you want to analyze arbitrary codebases outside the mdBook workflow, you’re building simple tutorials where manual diagrams suffice, or you can’t dedicate time to troubleshooting nightly toolchain integration issues. This is specialized educational infrastructure, not a general-purpose analysis tool—choose it when teaching ownership concepts is your primary goal and the integration complexity is worth the pedagogical benefit.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/cognitive-engineering-lab-aquascope.svg)](https://starlog.is/api/badge-click/developer-tools/cognitive-engineering-lab-aquascope)