Performance First
Rendering is a single linear pass over the document with width-bounded look-ahead — no backtracking, no intermediate materialization, no per-node allocation on the render path. Latest local Criterion means (cargo bench --bench bench, Linux x86_64 / WSL2, Rust stable, release build):
| Workload | Layout | Time |
|---|---|---|
| JSON tree (~85 nodes, ~340 B out) | flat (fits) | ~1.8 µs (~550 MiB/s) |
| JSON tree (~85 nodes) | broken (reflowed) | ~4.0 µs |
| Call, 128 arguments | flat | ~1.9 µs |
| Call, 128 arguments | broken | ~2.7 µs |
| Call, 8 arguments | flat | ~190 ns |
Numbers vary by CPU and environment; run the suite on your target to establish a baseline. Building a Doc is one Rc allocation per combinator; rendering reuses a single work stack and writes straight into the output buffer.
Features
- Eight combinators, any language —
text,line,softline,hardline,append,nest,group, plusconcat/join. Build aDocfrom any AST and render. - Fit-aware reflow —
grouplays its contents flat when they fit the remaining width and breaks them all-or-nothing when they do not. - Linear time, no recursion — Wadler/Lindig layout with bounded look-ahead; deep documents render and drop without overflowing the stack.
- Stream or collect — render to a
String, into anycore::fmt::Write, or into astd::io::Write. no_std— needs onlyalloc; thestdfeature adds the I/O renderer.- Fully safe — no
unsafe,#![forbid(unsafe_code)]. - Property-tested — flat-layout, panic-safety, associativity, and idempotence invariants checked across randomized documents with
proptest.
Installation
[]
= "1"
# no_std (drops the io::Write renderer):
= { = "1", = false }
MSRV is 1.85+ (Rust 2024 edition).
Quick Start
use Doc;
// Build `f(a, b, c)` as a document that can break one-argument-per-line.
let call = text
.append
.append
.append
.group;
// Wide: it all fits on one line.
assert_eq!;
// Narrow: the group breaks and the arguments stack, indented.
assert_eq!;
A JSON pretty-printer, end to end
A single walk over a value tree produces a document that renders as dense one-liners where they fit and indented blocks where they do not — with no width logic in the walk. See examples/json.rs for the full version.
use Doc;
// Wrap a body between brackets so it collapses to `open body close` when it
// fits and becomes a multi-line block otherwise.
let array = bracket;
assert_eq!;
assert_eq!;
How the layout engine works
A Doc is a small tree of nodes: literal text, flexible breaks (line, softline, hardline), concatenation, nest (indentation), and group (a layout choice point). Rendering walks the tree once with an explicit work stack, carrying a mode — flat or broken — and the current column.
At each group the engine asks a single question: do the group's contents, laid out flat, fit in the width left on this line? The look-ahead that answers it scans only as far as the first newline or the first column past the target width, which is what keeps the whole algorithm linear. If the contents fit, every flexible break inside becomes its flat form — a space or nothing. If they do not — or the group contains a hardline — every flexible break inside becomes a newline plus the current indentation. The decision is all-or-nothing for the breaks a group directly owns; nested groups are decided independently, so an inner list can stay flat inside an outer one that broke.
Because both the render pass and the look-ahead use heap work stacks rather than the call stack, a document nested tens of thousands of levels deep renders without a stack overflow — and is torn down the same way when dropped.
API Overview
For the complete reference with examples, see docs/API.md.
Doc— the layout document; cheap to clone (Rc-backed).
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
✅ | Adds Doc::render_writer for io::Write sinks. The crate core is no_std + alloc. |
Testing
The property suite in tests/proptests.rs checks the core invariants — the flat layout matches an independently-built oracle, rendering never panics at any width, append is associative, and group is idempotent — across randomized documents.
Cross-Platform Support
The layout engine is pure computation with no platform-specific code, so it behaves identically everywhere Rust runs. CI covers Linux, macOS, and Windows on both stable and the 1.85 MSRV.
Contributing
See REPS.md for the engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.