A New Design for Pretty Printer Implementations in Rust

g0xA52A2A1 pts0 comments

A New Design for Pretty Printer Implementations in Rust May 28, 2026<br>9 mins read<br>Table of Contents<br>A New Design for Pretty Printer Implementations in Rust<br>Since I studied Rustc’s pretty printer and implemented my own pretty printer library (which is used in the cgrammar crate, a crate for parsing and processing C23 syntax), I have been thinking about how to design a better pretty printer library, especially by applying the research results from academia 11.J. Hughes, “The Design of a Pretty-Printing Library,” Advanced Functional Programming, vol. 925. Springer Berlin Heidelberg, Berlin, Heidelberg, pp. 53–96, 1995. doi: 10.1007/3-540-59451-5_3. 22.P. Wadler, “A Prettier Printer,” The Fun of Programming. Macmillan Education UK, London, pp. 223–243, 2003. doi: 10.1007/978-1-349-91518-7_11. 33.J.-P. Bernardy, “A Pretty but Not Greedy Printer (Functional Pearl),” Proceedings of the ACM on Programming Languages, vol. 1, no. ICFP, pp. 1–21, Aug. 2017, doi: 10.1145/3110250. to practical Rust library designs.<br>An important obstacle is that academic research is often based on functional programming languages, and in particular assumes the existence of garbage collection, whereas Rust is a systems programming language without garbage collection. This means that memory management must be an extra consideration when implementing these designs in Rust.<br>For example, in a typical Wadler-style44.For the differences between Wadler-style and Oppen-style pretty printers, see this explanation in the elegance crate’s documentation. pretty printer, the document structure is usually a recursive data type:<br>type doc =<br>| Nil<br>| Append of doc * doc<br>| Group of doc<br>| FlatAlt of doc * doc<br>| Nest of int * doc<br>| Hardline<br>| Text of string<br>| Union of doc * doc<br>| FailThis document structure is then converted into a string output through a layout function:<br>let rec layout (d : doc) (width : int) : string<br>= (* implementation omitted *)To implement such an algorithm in Rust, the most straightforward approach is to adopt the same pattern. For instance, the pretty crate implements Wadler’s classic algorithm (with some later supplements), and its document structure definition (simplified) is as follows:<br>pub enum DocT> {<br>Nil,<br>Append(T, T),<br>Group(T),<br>FlatAlt(T, T),<br>Nest(isize, T),<br>Hardline,<br>Text(Boxstr>),<br>Union(T, T),<br>Fail,

type BoxDoc = BoxDocBoxDoc>>;<br>type RcDoc = RcDocRcDoc>>;As shown above, the pretty crate makes the Doc enum generic over a type T, which can be either BoxDoc, RcDoc, or any other pointer type of Doc. This allows users to make more flexible memory management decisions: using the lighter BoxDoc, the more flexible but heavier RcDoc, or some arena allocators. 55.Another benefit is that this design effectively turns Doc from a type into a functor, enabling the use of functor-related abstractions (like catamorphisms) to implement efficient recursive algorithms; see the recursion crate. However, this design also has some limitations. For example, it forces all nodes in a document tree to use the same memory management strategy, which may lack flexibility in certain situations.<br>Oppen-style pretty printers (such as Rustc’s pretty printer and the elegance crate) do not have this problem because Oppen-style processes the document’s input and output as a stream without building a complete document tree. But the streaming approach of Oppen-style pretty printers also limits their expressive power. Research by Sorawee Porncharoenwase, et al. 66.S. Porncharoenwase, J. Pombrio, and E. Torlak, “A Pretty Expressive Printer,” Proceedings of the ACM on Programming Languages, vol. 7, no. OOPSLA2, pp. 1122–1149, Oct. 2023, doi: 10.1145/3622837. points out that the general pretty printing problem is a global optimization problem; streaming pretty printers can only obtain a locally optimal solution through greedy algorithms and cannot guarantee global optimality. Therefore, to achieve a globally optimal pretty printer, one must build the complete document tree.<br>This post will propose a new design for pretty printer implementations in Rust, aiming to retain the expressive power of Wadler-style document trees while implementing them in a way that better aligns with Rust’s memory management.<br>The inspiration for this implementation comes from a concept in functional programming: a data type can be equivalently represented by the ways you consume it.77.See my other blog post Church Encoding, Parametricity, and the Yoneda Lemma. We are not really interested in the concrete structure of the document tree, but rather in how to use it to produce output.<br>Therefore, instead of defining a recursive data structure for Doc, we can define Doc as a trait, with a method that consumes the document and produces the output.<br>pub trait Doc {<br>fn layout(&self, renderer: &mut Render) -> RenderOutput;<br>}The various document constructions then become different types implementing this trait, for example:<br>#[derive(Clone)]<br>pub struct Text {<br>s: String,

impl Doc for Text {<br>fn...

pretty printer document rust design type

Related Articles