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:
dbg!(&value)walks anyDebug + Serializevalue through serde into atoms and relations, then appends an envelope containing its expression, source, time, and thread.- If a type derives
SpytialDecorators, its link-time registration contributes layout and styling rules automatically, including rules from nested types. - 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
- Getting started — install, your first diagram, and where the output goes.
- Decorators — every layout rule, plus a worked red-black tree.
- Running headless & in Docker — CI, containers, no-display environments.
- Library API — using spytial as a dependency instead of a debug macro.
- Relationalization and reification — how Serde values become atoms and relations, and how they are reconstructed.
The longer design argument is on Brown PLT's blog: Diagramming Program Values by Spatial Refinement. Generated rustdoc is on docs.rs.