So, the comparison with malloc
(or global allocator) is apt, but it's important to understand why you don't need to pass the global allocator around - Firstly, the user can't safely destroy it and secondly, an allocation from it doesn't imply a borrow on the allocator. The first property allows the second. You could implement this yourself, but it would require unsafe
.
I wrote an example of an alternative approach using thread_local
and generational-arena
. It does not require any unsafe
code. generational-arena
is a special type of arena that, essentially, defers borrow analysis to runtime. When you allocate a node, it returns a handle to a value called Index
that you can later use to retrieve a temporary, concrete borrow.
Note that I used thread_local
here, but if you need to be able to share the arena across threads, then lazy_static
should work, too (replace RefCell
with Mutex
). Additionally, if you want to be able to swap in-and-out different arenas, you could instead have it be something like RefCell<Option<Arena<T>>>
, which would let you provide the "implicit" arena only temporarily.
The main upside of the generational-index approach: It satisfies the requirement to not have to pass the arena around.
Unfortunately, it has some downsides: The code is a bit ugly. You lose static borrow checking AND static type checking. And you have to manually deallocate values from the arena using remove
, though this may not be a problem if deallocation of nodes can be deferred to destruction of the arena without having to worry about memory footprint.
This is a bit of a fundamental problem if you want a global arena - all allocations from the arena imply a borrow on the arena. And there's no way to statically assert to the compiler that the arena will live longer than all of its allocations without using unsafe
. If you're willing to write unsafe
code, then it becomes a bit easier, but you have to be careful - when you erase the borrow on the arena, it is now up to you to personally validate that 1) the arena will never invalidate the borrow and 2) the arena will never be deallocated.
I'll leave it to you on which of these three approaches is best:
- Just pass a
typed-arena
style of arena (and its lifetime) around. You then have bare references, but pay the tax of passing it around.
- Use a global arena with lifetime-erased allocation handles like
generational-arena
.
- Get out
unsafe
and start doing developer-time borrow checking.