# gc-arena: a garbage collected arena for Rust that refuses to run during a collection

> gc-arena gives Rust exact, cycle-detecting garbage collection inside a closed arena, made safe by a branded lifetime, a Collect trait and a rule that mutation and collection never overlap. That rule is the whole design, and it is why the crate fits a game VM loop and not a general purpose application.

**kyren/gc-arena** — Incremental garbage collection from safe Rust

- Repository: https://github.com/kyren/gc-arena
- Stars: 898 · Forks: 60
- Language: Rust
- License: CC0-1.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/kyren-gc-arena

## Three techniques hold the safety argument together

The README lists the mechanisms that make the arenas safe, and it is worth reading them as a single argument rather than three separate features.

The first is the Collect trait, which is used to implement exact tracing. The documentation is direct that this trait is unsafe and that implementing it incorrectly can lead to UB, and that it can be, and almost always is, safely derived via a proc macro. That is the shape of the whole crate. There is an unsafe core, and the ergonomic path is a derive macro that generates the correct implementation for you. The helper crate gc-arena-derive exists for exactly this.

The second is branding. Pointers are branded with a unique generative 'gc lifetime, which ensures that they cannot escape mutation callbacks or be assigned to the wrong arena. This is the mechanism that stops a Gc from one arena being used with another, and it is generative, meaning the lifetime is created fresh and cannot be named or forged from outside. The prior art section credits the idea of using generativity to a source titled You can't spell trust without Rust, which tells you the idea is borrowed rather than invented here.

The third is the constraint that gives the crate its shape: mutation xor collection. Either Arena::mutate is executing or Arena::collect is executing, but never both at the same time. Because of that, the collector knows there are no untraceable objects sitting on the stack when it runs. Every object in the arena is reachable from a root the collector can see, so there is no need for a conservative scan of the stack.

That last technique is what buys exactness, and exactness is what buys cycle detection. A conservative collector cannot tell a stale pointer from a live one, so it cannot collect a cycle. Refusing to run while application code is on the stack is the price of knowing exactly what is alive.

## Collection is incremental, and allocation debt buys the pause time

The algorithm is described as an incremental mark-and-sweep very similar to the one in PUC-Rio Lua, and it is optimised primarily for low pause time. That is the design target, and the mechanism is a debt counter.

During mutation, allocation debt is accumulated, and that debt determines how much work the next call to Arena::collect will do. So instead of a stop-the-world collection that has to trace everything, the collector does a slice of work each time you call it, and the amount of that slice is set by how much you allocated since the last call. A frame loop that allocates a predictable amount each frame gets a predictable amount of collection work each frame.

The Lua lineage is not incidental. The prior art section says the design of the actual garbage collection system borrows heavily from the incremental mark-and-sweep collector in Lua 5.4, and the user-facing design borrows heavily from rust-gc, with the collector itself being the main Lua-derived part.

The pointers themselves are cheap. Gc<'gc, T> is described as a zero-cost newtype around a *const T that implements Copy, and the documentation notes that no pointer bookkeeping at all is necessary because Gcs are moved or copied during mutation. That is the payoff of the branding approach: because the lifetime carries the guarantee statically, the runtime representation does not have to carry a root table entry or a write barrier for every pointer.

There is also support for slices, strs and other dynamically sized types allocated directly in Gc pointers, and those pointers can have either a fat or a thin representation, with the thin one reading pointer metadata from the garbage collector object header. That is the kind of detail that only matters if you are actually storing heterogeneous sized data in the arena, and it is the difference between a collector that handles a Vec<T> and one that handles a str.

## The linked list example is the smallest complete program

The README points at a linked list example in the examples directory and says it explains everything necessary to fully understand the machinery, which is a strong claim for an example file. It also links a blog post, described as an in-depth tour of the crate's design and as quite dense.

The repository layout is small: a src directory, a derive directory for the proc macro, an examples directory, a tests directory, a Cargo.toml, a CHANGELOG.md and the two licence files. There is one example file listed, examples/linked_list.rs.

The workspace configuration shows how the crate is put together. The workspace has a single member, derive, and the shared package metadata sets version 0.7.0, edition 2024, and the authors. The main package is named gc-arena and described as safe, incrementally garbage collected arenas, and it pulls the derive crate from the local path at the matching version.

The features section is short and worth looking at directly:

```toml
[features]
default = ["std"]
std = []
tracing = ["dep:tracing"]
```

So the default build includes the standard library, and there is a separate tracing feature that pulls in the tracing crate. The optional dependencies are a set of choices for the internal data structures: enum-map, hashbrown, indexmap, slotmap and smallvec, each marked optional and each built with default features off, which means the crate can be pointed at a different collection implementation for whatever it needs internally. The tracing dependency is optional in the same way, and the dev dependencies are rand and trybuild, the latter being the standard way to test that compiler error messages come out as expected.

That is a crate with a small surface and a lot of care in the internals, and the example and the blog post are where the care is documented.

## Mutation exclusive of collection is the constraint that decides the fit

The use cases section is where the project is most honest, and the honesty is the most useful thing in the README.

The design means Arena::mutate must return for collection to be performed, and therefore code that uses garbage collected objects cannot be running while collection takes place. The documentation calls this a major limitation to consider, while also noting it is not as much of a problem as it sounds, because there is a built-in mechanism for rooted Gc pointers that is fairly cheap.

The consequence is that the crate can be used more from the outside than from within Arena::mutate. Used that way, it is more like other garbage collector systems for Rust that primarily use rooted pointers that are not zero-cost, and Arena::mutate then acts as an accelerator for as-cheap-as-possible pointer manipulation. In other words, you can have a zero-cost inner loop for the code that manipulates objects, and a slower rooted style for everything that surrounds it.

Then the documentation says sometimes mutation xor collection is not even a problem. Video games, or programs similar to video games, generally want to run VM code that returns once per frame and then run some garbage collection once per frame. If you are writing a VM used that way, the crate may be perfect for you, and the documentation says this was the original use case and what initially formed its design: writing fast VMs for scripting languages used in video games in safe Rust.

That is a specific and confident claim about the fit, and it is the right lens. The question to ask is not whether the collector is fast but whether your program has a natural tick. If it does, the constraint vanishes into the frame boundary. If it does not, you will be restructuring your code around a rule that was never meant to apply to it.

## Real users, and the limits the project lists itself

The current status section is short and blunt: basically usable and safe. Then it names the users.

The crate is used by the Adobe Flash Player emulator Ruffle for its ActionScript VM, and by the author's own GameMaker compatible compiler and VM, fabricator, which is used by the game Fields of Mistria. Two independent real users, one of them a well known emulator project, is a stronger signal than any benchmark would be, because both of them are interpreter-style workloads with a per-frame tick, which is exactly the shape the design was built for.

The list of other limitations is given in the project's own words. Generators, or just plain async, can be used with some effort to work around the fact that collection cannot run while garbage collected code is running, and the documentation notes this would be much easier if it were possible to generate Collect implementations for generator state machines, but there is currently no way to do that in Rust nor any planned feature that would enable it. That is a candid statement about a missing language feature, and it is the one that would bite a team using async throughout.

Second, the crate is designed for single-threaded use with multiple independent arenas, and has no support for multi-threaded mutation or collection. So the parallelism story is multiple independent arenas, which is not the same as one arena shared across threads.

Third, there is currently no mechanism for moving allocated objects or for heap compaction. The objects stay where they were allocated. For a long-running VM that allocates and frees heavily, that means the heap fragments and the memory is not reclaimed for reuse, which is a different failure mode from a collector that is merely slow.

## Licence, cadence, and how to actually try it

The licensing is unusual and worth understanding before you adopt it. Everything in the repository is licensed under either the MIT license or the Creative Commons CC0 1.0 Universal Public Domain Dedication, at your option. The project record lists the licence as CC0-1.0, and the repository carries both LICENSE-MIT and LICENSE-CC0 files.

For a crate, MIT is the part you will care about, and it is the permissive end of the scale. The CC0 option matters for the documentation and comments rather than the code, but offering it means you can lift prose and examples without worrying about attribution.

The release record shows steady work with a recent burst. Version 0.7.0 and 0.6.1 both landed on 2026-08-08, 0.6.0 on 2026-06-05, and the last push to the repository was on 2026-08-17. The repository is not archived. The crate is still on a 0.x line, so the semver promise of a 1.0 release, that the interface will not break in a minor version, does not formally apply yet.

There is no install command in the documentation to copy, because this is a library you add as a dependency rather than a binary you run. The package is published on crates.io as gc-arena, the documentation is on docs.rs, and the crate is imported by name in your own Cargo.toml, with the derive crate pulled in automatically as a dependency of it. The one thing worth checking before you start is the toolchain, because the package declares edition 2024, which means a recent stable Rust is needed, and a project pinned to an older compiler will not build it.

## Conclusion

gc-arena fits a Rust project that needs cycle-detecting garbage collection and can structure its work into a mutate phase and a collect phase, which in practice means a scripting language VM, a game engine layer or an interpreter with a per-frame tick. It is the wrong choice for a multithreaded application, since the crate is designed for single-threaded use with multiple independent arenas and has no support for multi-threaded mutation or collection, and it is the wrong choice when allocation must happen while other code is already running inside a mutation. Before adopting it, read the design post the README links, confirm that your call sites can all be brought inside Arena::mutate, and check the rust-version floor of your toolchain against the crate's edition 2024 requirement, because a collector that cannot run while your code runs is a constraint you cannot refactor away later.

## FAQ

### What is gc-arena and what does it provide?

It is a Rust crate that gives Rust garbage collected arenas and a safe way to interact with them, providing allocation with safe, incremental, exact, cycle-detecting garbage collection inside a closed arena. The crate is described as basically usable and safe.

### How does gc-arena make garbage collection safe in Rust?

Through three techniques: the Collect trait, which is unsafe but can be derived via proc macro, implements exact tracing; pointers are branded with a unique generative 'gc lifetime so they cannot escape mutation callbacks or reach the wrong arena; and mutation and collection are mutually exclusive, so the collector knows no untraceable objects are on the stack.

### What is the main limitation of gc-arena?

Arena::mutate must return before collection can run, so code using garbage collected objects cannot be running during a collection. Rooted Gc pointers make the crate usable from outside the mutate closure, and the design suits programs such as video games that return once per frame and collect once per frame.

### Is gc-arena multithreaded?

No. The crate is designed for single-threaded use with multiple independent arenas, and it has no support for multi-threaded mutation or collection. There is also currently no mechanism for moving allocated objects or for heap compaction.

### Who uses gc-arena and under what licence is it released?

It is used by the Ruffle Flash Player emulator for its ActionScript VM and by the fabricator GameMaker compatible compiler and VM. Everything in the repository is licensed under either the MIT license or CC0 1.0, at your option.

## Sources

- [Issues](https://github.com/kyren/gc-arena/issues)
- [kyren/gc-arena on GitHub](https://github.com/kyren/gc-arena)
- [License: CC0-1.0](https://github.com/kyren/gc-arena/blob/master/LICENSE)
- [README](https://github.com/kyren/gc-arena/blob/master/README.md)
- [Releases](https://github.com/kyren/gc-arena/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kyren-gc-arena
