pprint 0.3.6

Flexible and lightweight pretty printing library for Rust
Documentation

pprint

A Rust library for pretty printing using a document model. Automatically derive Pretty for structs, enums, and primitive types; vector and map types are also supported by default; very similar to the derive(Debug) macro, just prettier and more configurable.

Usage

use pprint::{Doc, PRINTER, pprint, join, wrap};

let items: Vec<Doc> = vec![1, 2, 3].into_iter().map(Doc::from).collect();
let doc = wrap("[", "]", items.join(Doc::from(", ") + Doc::Hardline));

print!("{}", pprint(doc, PRINTER));
// prints:
// [
//   1,
//   2,
//   3
// ]

Document Model

The document model provides a rich set of building blocks:

  • Primitive values like strings, numbers
  • Containers like vectors, tuples, maps, sets
  • Formatting like concat, join, smart_join, wrap, group
  • Indentation control with indent and dedent
  • Conditional formatting with if_break
  • Line breaks like hardline, softline

The Printer handles pretty printing a Doc to a string with configurable options:

  • max_width - maximum width of each line
  • indent - number of spaces for each indentation level
  • use_tabs - use tabs instead of spaces for indentation

Two entry points:

  • pprint(doc, printer) — consumes the Doc and renders to String
  • pprint_ref(&doc, printer) — borrows the Doc without consuming or cloning it; useful for benchmarks and repeated renders (e.g., LSP formatting)

Derive Macro

The derive macro allows for easy pretty printing of essentially any type. Here's a trivial example:

#[derive(Pretty)]
struct Point {
    x: f64,
    y: f64
}

let point = Point { x: 1.0, y: 2.0 };
print!("{}", Doc::from(point)); // prints "(x: 1, y: 2)"

Pretty supports an additional attribute, pprint, which is used to customize an object's pretty printing definition. The following options are available:

  • skip: bool: Skip this field - don't include it in the output
  • indent: bool: Indent this field - add a newline and indent before and after
  • rename: Option: Rename this field - use the given string as the field name
  • getter: Option: Use the given function to get the value of this field
  • verbose: bool: Verbose output - include field names in output
#[derive(Pretty)]
#[pprint(verbose)]
struct Point {
    #[pprint(rename = "x-coordinate")]
    x: f64,
    #[pprint(rename = "y-coordinate")]
    y: f64
    #[pprint(skip)]
    _skip_me: bool,
}

let point = Point { x: 1.0, y: 2.0, _skip_me: true };

print!("{}", Doc::from(point));

// prints:
// Point {
//   x-coordinate: 1,
//   y-coordinate: 2
// }

Structures can be arbitrarily nested. More involved examples can be found in the tests file.

smart_join

smart_join's implementation is based off the text justification algorithm: text_justify

text_justify uses greedy bin-packing: items are packed left-to-right into lines, breaking when the next item would exceed max_width. O(n) for all inputs.

For more information on the algorithm in particular, see the above's heavily commented source code, or the wonderful Lecture No. 20 from MIT's 6.006 course, "Introduction to Algorithms".

Performance

Throughput varies by workload -- leaf-heavy documents (integers, strings) are close to Debug, while smart_join adds DP overhead for optimal line breaking.

Benchmark pprint (ns) Debug (ns) Ratio
flat_vec_1k (ints) 22,929 21,868 1.05x
flat_vec_10k (ints) 224,962 233,289 0.96x
nested_100x100 252,621 327,348 0.77x
floats_1k 28,456 67,833 0.42x
strings_1k 95,064 47,014 2.02x
tuples_1k 262,228 161,919 1.62x

Render Optimizations

Several optimizations target the render pipeline, particularly for code formatting workloads (CSS, JSON) where throughput exceeds 1,400 MB/s on the Doc-to-string phase:

  • No full-tree pre-pass. Output buffer starts at fixed 1024 capacity. count_text_length is called lazily by Group and Join only when needed.
  • LinearJoin variant. Inline break decisions during render with no text_justify pre-pass. Best for code formatting where optimal prose justification is unnecessary. Emitted by bbnf codegen for non-text-justify modes.
  • Reverse cursor in SmartJoin. Break checks use a reverse-iterating cursor (O(1) per item) instead of binary_search (O(log n)).
  • FxHashMap pre-allocated. text_length_cache uses rustc-hash FxHashMap with 256 initial capacity, avoiding rehash overhead.
  • text_justify memo pooling. The doc_lengths buffer is reused across SmartJoin calls (cleared, not reallocated).
  • Release from_utf8_unchecked. All Doc sources produce valid UTF-8, so release builds skip validation.

In the gorgeous CSS formatter, the pprint render phase achieves ~1,140 MB/s on bootstrap.css (281KB), making it negligible compared to parsing and Doc construction.

See the benches directory for more information.

About

Contributions and suggestions welcome — open an issue or pull request.