Spytial for Rust

See the shape of your data, not the braces. Spytial is a drop-in replacement for std::dbg! that opens an interactive diagram of a Rust value in your browser.

When you want to know the shape of the tree your program just built, you reach for dbg! and start counting braces:

[src/main.rs:42:17] tree = RBTree {
    root: Some(RBNode { key: 38, color: Black, left: Some(RBNode {
    key: 19, color: Black, left: Some(RBNode { key: 12, color: Black,
    left: Some(RBNode { key: 8, color: Red, left: None, ...

That's a red-black tree, but you have to rebuild it in your head from the indentation. Spytial draws it for you — change one word:

- std::dbg!(tree)
+ spytial::dbg!(tree)

Your terminal output is unchanged; a browser viewer also opens with a diagram of the value. Repeated calls append to that same viewer, creating an ordered execution trail instead of a row of tabs. spytial::dbg! takes the same arguments as std::dbg! and returns the value the same way, so you can drop it in anywhere std::dbg! already appears.

Run it now

Add spytial to a project (cargo add spytial serde --features serde/derive), then:

use spytial::{dbg, SpytialDecorators};
use serde::Serialize;

#[derive(Debug, Serialize, SpytialDecorators)]
#[attribute(field = "key")]
struct Node {
    key: u32,
    left: Option<Box<Node>>,
    right: Option<Box<Node>>,
}

fn main() {
    let tree = Node {
        key: 5,
        left: Some(Box::new(Node { key: 3, left: None, right: None })),
        right: Some(Box::new(Node { key: 7, left: None, right: None })),
    };
    let _ = dbg!(tree);
}

cargo run prints the usual dbg! line to your terminal and opens the tree as the first capture in your browser. Getting started walks through the same example line by line. The SpytialDecorators derive and #[attribute] are optional: any Debug + Serialize value gets a structural diagram, and these additions make that diagram more informative.

Shape the layout with decorators

The #[attribute] above is a decorator: a rule attached to a type that says what should hold about the layout, not how to draw it. Add a few more and the default graph becomes a proper tree — left children go down-and-left, nodes are colored by a field, and the empty leaves are hidden:

#![allow(unused)]
fn main() {
#[orientation(selector = "{x, y : Node | x->y in left}",  directions = ["left",  "below"])]
#[orientation(selector = "{x, y : Node | x->y in right}", directions = ["right", "below"])]
}

Decorators are registered at link time and collected across nested types, so you decorate Node once and every Node in the value picks up the rules. See Decorators for the full reference and a red-black tree built up one rule at a time.

How it works

Three steps, all built into the crate, so diagrams work offline with no network calls:

  1. dbg!(&value) walks any Debug + Serialize value through serde into atoms and relations, then appends an envelope containing its expression, source, time, and thread.
  2. If a type derives SpytialDecorators, its link-time registration contributes layout and styling rules automatically, including rules from nested types.
  3. That data and any decorators fill a self-contained HTML session, rendered in the browser by a bundled copy of spytial-core.

The live viewer polls an in-process server bound to an OS-selected port on 127.0.0.1; it never exposes captured values to the LAN and makes no external network requests. Failures never panic: a serialization error, a missing temp dir, a failed loopback bind, or no browser to open all log a one-line warning and return. dbg!(x) always returns x.

Where next

The longer design argument is on Brown PLT's blog: Diagramming Program Values by Spatial Refinement. Generated rustdoc is on docs.rs.