Closing a three-year-old issue using Rust arenas

Lobsters Hottest News

Summary

A Gleam core team member improved the language's pretty printer performance by replacing boxed documents with arena-allocated references, reducing peak memory usage by 10% and closing a three-year-old issue.

<p><a href="https://lobste.rs/s/7840ca/closing_three_year_old_issue_using_rust">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 07/12/26, 08:53 PM

# Closing a three-year-old issue using Rust arenas Source: [https://giacomocavalieri.me/writing/gleam-rust-arenas](https://giacomocavalieri.me/writing/gleam-rust-arenas) I am part of the core team working on[Gleam](https://gleam.run/), a small, friendly functional programming language written in Rust\. A little while ago I ran into a three\-year\-old[issue](https://github.com/gleam-lang/gleam/issues/2251)suggesting how we could start using arenas to make the language's pretty printer faster\. Quite ominously the issue ends with: > *"This would be quite a long manual job\."* That sounds grand\. I find it actually quite fun to tackle those boring and repetitive jobs where I can turn my brain off and just punch at a keyboard\.[^llms](https://giacomocavalieri.me/writing/gleam-rust-arenas#^llms-footnote) After a couple of days of careful find\-and\-replace and a[`\+2963/\-1032`pull request](https://github.com/gleam-lang/gleam/pull/5824)later, I managed to make the Gleam formatter a lot faster, also cutting its peak memory usage by a good 10%\! ## [\#](https://giacomocavalieri.me/writing/gleam-rust-arenas#what-s-the-problem)What's the problem? As the issue points out, Gleam's pretty printer is based on a recursive data structure, the`Document`\. Here's a slightly simplified version of the one that Gleam uses: ``` pub enum Document<'string> { /// A literal string that will be printed /// exactly as it is. String(&'string str), /// A possible point where the rendered /// document could be broken if any line /// line gets too long. Break { /// The string to render if this is broken broken: &'string str, /// The string to render if this in not broken unbroken: &'string str }, /// A document that can be broken along its /// `Break`s. Group(Vec<Self>), /// If this document is broken, increase its nesting /// by some amount. Nest(Box<Self>), // ...and many more... } ``` This allows us to describe how a piece of code should be rendered and how the pretty printer is allowed to break it if it gets too long for the line limit\. For example, this is how a list is represented: ``` Group(vec![ String("["), Nest(Box::new(Break { broken: "", unbroken: "" })), String("1"), Nest(Box::new(Break { broken: ",", unbroken: ", " })), String("2"), Break { broken: ",", unbroken: "" }, String("]") ]) ``` This means the formatter is allowed to either render the list without ever breaking it, or \(if it doesn't fit on the current line\) to break it along the given break points:[^how\-this\-works](https://giacomocavalieri.me/writing/gleam-rust-arenas#^how-this-works-footnote) ``` // Rendered with none of the `Break`s broken... [1, 2] // ...or rendered breaking it along the `Break`s! [ 1, 2, ] ``` Since some`Document`variants can hold other documents, like`Nest`does, those will have to be boxed on the heap\. And quite a good chunk of time could be spent just doing that\! ## [\#](https://giacomocavalieri.me/writing/gleam-rust-arenas#arenas)Arenas What if instead we could just store references to other documents? The`Document`enum would need some updating\. We need a new lifetime for those references: ``` pub enum Document<'doc, 'string> { - Nest(Box<Self>), + Nest(&'doc Self), // ...and the other variants... } ``` If you've worked with references in Rust you know it can sometimes be quite a bit of a pain to deal with lifetimes\. But using an arena can make it a lot nicer\. In our case I've decided to use the[`typed\_arena`](https://docs.rs/typed-arena/latest/typed_arena/)crate\. The API is pretty straightforward: you can`alloc`new things on the arena, and get a reference back\. That's basically it\! As long as the arena is alive, you will be able to use the data in it; and when the arena gets out of scope and is dropped, all the data will be dropped with it\. ``` let arena = Arena::new(); let comma_break = arena.alloc(Break { broken: ",", unbroken: ", " }); let nested_break = arena.alloc(Nest(comma_break)); // ...render the docs or whatever... ``` The borrow checker will make sure that we can't reference data in the arena once the arena is dropped: ``` pub fn alloc(&self, value: T) -> &mut T // ^ ^^^^ // The reference to the value allocated in // the arena can't outlive it! ``` Another nice benefit is that we can cache a lot of documents that are repeated throughout the code without having to allocate one every single time\. For example the documents with all the language's keywords`String\("fn"\)`,`String\("pub"\)`,`String\("type"\)`; or the comma we use to separate list items:`Break \{ unbroken: ", ", broken: "," \}`\. There's literally[hundreds of little documents](https://github.com/gleam-lang/gleam/pull/5824/changes#diff-fd77bddd869b2a4e981c876948b9c61e195184ef23a2becbc4ac023aed51f920R590)that are allocated just once rather than being constantly boxed\. ## [\#](https://giacomocavalieri.me/writing/gleam-rust-arenas#the-result)The result As nice as the arena is to use, changing a big chunk of code to start using it required a bit of grunt work: all the bits of code that previously were fine with just`Box::new`, now need to take the arena where the data will be allocated as an additional argument: ``` const comma_break = Break { broken: ",", unbroken: ", ") }; pub fn format_list( + arena: &'doc arena : Arena<Document<'doc, 'string>>, items: Vec<UntypedEpxression> ) -> Document<'string> { let comma = - Nest(Box::new(comma_break)); + arena.alloc(Nest(comma_break)); Group(vec![ String("["), items .iter() .map(|item| - Nest(Box::new(format_expression(item))) + Nest(format_expression(arena, item)) ) .intersperse(comma) .collect(), String("]") ]) } ``` In the end, the outcome was much better than I anticipated, the pretty printer alone got a huge speedup\. The time spent formatting a real Gleam project like[`squirrel`](https://github.com/giacomocavalieri/squirrel)went from 13ms to 9,8ms\.*That's 24% faster\!* The formatting is just a fraction of what goes into running`gleam format`, we first have to read and parse the source code of a project\. And yet the improvement is actually noticeable, running`gleam format`is 13% faster\. The peak memory footprint also went down from 8\.4MB to 7\.6MB\. As it turns out spending less time allocating stuff on the heap can make our code faster and less memory hungry, and arenas are a really nice way to do that in Rust\. --- [^how\-this\-works](https://giacomocavalieri.me/writing/gleam-rust-arenas#^how-this-works-origin)The pretty printing algorithm itself is quite fascinating, if you're curious to learn how this works you can check the["Strictly Pretty"](https://lindig.github.io/papers/strictly-pretty-2000.pdf)paper out\. I found it a really nice read, and it's what the Gleam pretty printer is based on\! [^llms](https://giacomocavalieri.me/writing/gleam-rust-arenas#^llms-origin)This is also one of the resons why I despise LLMs so much, and you won't see me use them: I like typing and thinking for myself, thanks\!

Similar Articles

Rewriting Bun in Rust

Hacker News Top

Bun, the JavaScript runtime and toolchain, is being rewritten from Zig to Rust to improve memory safety and stability, addressing a long tail of use-after-free and memory leak bugs.

A New Design for Pretty Printer Implementations in Rust

Lobsters Hottest

A blog post exploring a new design for pretty printer implementations in Rust, addressing challenges of adapting functional programming research to a systems language without garbage collection, and comparing existing approaches like the 'pretty' crate and Oppen-style printers.

Bun Has Been Converted to Rust. Now What?

Hacker News Top

Anthropic acquired Bun and used Claude Code agents to rewrite the entire runtime from Zig to Rust in nine days. The rewrite passed 99.8% of tests but introduced over 10,000 unsafe blocks, raising questions about the benefits of memory safety.

Gossamer: a Rust-flavoured language with real goroutines and pause-free memory

Hacker News Top

Gossamer is a new programming language inspired by Rust that features real goroutines, pause-free deterministic memory management with reference counting and arenas, and a bytecode VM with LLVM compilation. It aims to provide expressive syntax without a borrow checker or garbage collector pauses.