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 ;
let doc = from
.join
.wrap;
print!;
// 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
indentanddedent - 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 lineindent- number of spaces for each indentation leveluse_tabs- use tabs instead of spaces for indentation
Two entry points:
pprint(doc, printer)— consumes theDocand renders toStringpprint_ref(&doc, printer)— borrows theDocwithout consuming or cloning it; useful for benchmarks and repeated renders (e.g., LSP formatting)
Derive Macro
Half of the library's development time was spent on the derive macro, allowing for easy pretty printing of essentially any type. Here's a trivial example:
let point = Point ;
print!; // 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
let point = Point ;
print!;
// prints:
// Point {
// x-coordinate: 1,
// y-coordinate: 2
// }
Structures can be arbitrarily nested, & c. & c. 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
For n <= 32 items, text_justify uses the full O(n^2) DP algorithm. For n > 32, it falls back to an O(n) greedy packing heuristic to avoid quadratic overhead on large join lists.
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) | 33,025 | 21,446 | 1.5x |
| flat_vec_10k (ints) | 307,533 | 222,306 | 1.4x |
| nested_100x100 | 293,817 | 327,671 | 0.90x |
| floats_1k | 43,227 | 67,748 | 0.64x |
| strings_1k | 109,014 | 48,582 | 2.2x |
| tuples_1k | 270,835 | 153,970 | 1.8x |
See the benches directory for more information.
About
This library was partway created as a means by which to learn more about Rust's procedural macros, and partway because I just love pretty printing. It's a work in progress, but I'm fairly pleased with it hitherto. If you have any suggestions, please feel free to open an issue or a pull request.