Seeing Should allocators be passed in as a value or as a pointer? several days ago reminded me of a question I have regarding Ziglua.
Lua supports using a custom allocator for it’s internal allocations. For this to work, Ziglua currently uses a pointer to a std.mem.Allocator
for initalization, and I’m wondering if there is a better way to more closely follow Zig conventions. I’d appreciate any feedback you wonderful smart people might have.
Some context for those not familiar with Lua’s internals: A Lua state is created with lua_newstate
. Which accepts a lua_Alloc
function and custom data void pointer that is passed to the allocator function.
Because this custom data needs to be a pointer, the Ziglua init function uses a *std.mem.Allocator
so that can be stored by Lua internally and passed to the custom allocator function
So when initializing a Lua state with Ziglua the pointer to the allocator should be stable to prevent issues (you don’t want to use a pointer to stack memory that may be invalidated). But this is unconventional and I don’t want this to lead to issues.
A possible issue in the wild
I was reading through the comlink source recently and noticed the code passes an allocator pointer to the Lua.init()
function. It seems to work fine (maybe this function is inlined by llvm or the allocator argument is optimized to a pointer?) but I want to remove/minimize this possible error. I don’t point this out to call out comlink. I just want to show that this is a possible issue
Here are some ideas I have considered:
-
Documenting the function better to hopefully avoid mistakes. This is simple, but doesn’t prevent mistakes
-
Ziglua previously stored an allocator pointer inside a state struct, and the init function would create a
*std.mem.Allocator
to store a pointer internally (source here). When I switched from a struct to an opaque I removed the internal allocator pointer.A motivator for changing to an opaque was to remove the
allocator
field entirely because it was causing confusion. I could switch back to a struct though. I’m leaning toward this because it seems the least bad.I could possibly even create an allocator pointer, pass it to
lua_newstate
and then not store it as an extra field on the Lua struct, instead usinggetAllocFn
to get the pointer to free it when the Lua state is closed. But that also isn’t perfect becausesetAllocFn
could overwrite the data pointer and cause a leak. -
Require the
Lua
object to exist through a stable pointer so the init function has an address to store the allocator at. Something like thisvar lua: Lua = undefined; try lua.init(allocator);
but if someone decides to initialize Lua inside a function and then return the Lua state then there are still issues.
-
Lua has a concept of “extra space” which is a memory region associated with each Lua state. I could maybe store the Zig allocator pointer there, but that feels convoluted (and not much different from idea #2) and it could be overwritten by someone using the
lua_getextraspace
function.
tldr: I need a good way to have a stable pointer to a std.mem.Allocator
that Lua can access safely. Maybe I’m just overthinking things…