Where should Allocators be created?

Hi all, sorry if this has been asked before. I’m a C++ developer and as a first simple project to learn zig I’m making a simple 2D game using SDL. One thing that still isn’t clear for the language is where exactly an allocator should be created (to follow the usual way of doing things). I know that it’s pretty common for games to have more than one allocator type, such as a general allocator and an ArenaAllocator and they can be declared in different places.

But, for example, for a general allocator used to create std.ArrayList inside my game, should it be instantiated at the main() body and then passed to my Game object? For example Game.init(allocator)? In this case should the Game keep a copy or pointer to the allocator? Would it make more sense to create the allocator within a “manager” class or something like that?

I know this type of question has many answers, and that’s fine. I just would like to know what’s the usual way to do it in Zig.

Thanks a lot for the help!

Since zig-0.16.0, zig will setup a gpa (general-purpose allocator) for you, provided as an argument in main(). You can find the explanation and example code here: 0.16.0 Release Notes ⚡ The Zig Programming Language

You can use this allocator (which I will refer to as init.gpa) in the same way as you use malloc() and free() when programming in C. Ask it for memory, and when your done with the memory, give it back. If you don’t give it back, that memory is wasted (a.k.a. a ‘memory leak’) until the program terminates and the OS cleans up everything anyway.

Note that init.gpa is not the allocator itself - rather, it is the ‘standard memory allocation interface’. It’s how you do polymorphism in zig - there are many different allocators, but they are all interchangeable because they provide an identical Allocator interface.

init.gpa has the type std.mem.Allocator, documented here: Zig Documentation

It is a struct with two fields, both of them pointers. Thus, when you give init.gpa to a function accepting an Allocator, it has the same effect as passing an object by reference in C++.

... = struct {
    ptr: *anyopaque,
    vtable: *const VTable,
}

vtable points to a list of functions in the allocator’s implementation. In C++ terms, they are public methods.
ptr is passed as the first argument to each of the vtable functions. In C++ terms, it’s the this pointer.

For example, if you have multiple instances of std.heap.ArenaAllocator, each one will give an Allocator with the same vtable but different ptr.

I hope that clears things up.

Nice! This standard allocators seems very useful. So, in my case, I could just use this gpa and pass it as a parameter down to my other objects, I guess?

Yes, that’s the Zig paradigm: pass the allocator down.

The only thing I’ll add is in cases where you find yourself creating an allocator implementation, you only need make sure that the implementation outlives everything it allocates. For interfaces (i.e. std.mem.Allocator), they’re Copy in Rust terms and impl.allocator() is cheap for all implementations in the standard library, so you can be much more cavalier.