Hello,
When writing Zig I like to use the following pattern, as it allows me to swap out my allocator based on optimizemode or even build script flags (which ive never done before but I think there are probably tons of reasons to do this) in a nice way:
pub fn main() !void {
var alloc_impl = switch (builtin.mode) {
.Debug => std.heap.DebugAllocator(.{}).init,
else => std.heap.ArenaAllocator.init(std.heap.page_allocator),
};
defer _ = alloc_impl.deinit();
const allocator = alloc_impl.allocator();
_ = allocator;
// ...
}
const std = @import("std");
const builtin = @import("builtin");
The Problem is, that this stop working once I want to use an allocator like smp_allocator or c_allocator. I wonder if it would be a good idea to make all allocators be inside a struct, have init, deinit and allocator functions, or alternatively provide a wrapper for the ones that dont.
What is your opinion on this? Does anyone else even do this?
The Zig 0.14.0 Release Notes suggest to use something like
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
pub fn main() !void {
const gpa, const is_debug = gpa: {
if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };
break :gpa switch (builtin.mode) {
.Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
.ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
};
};
defer if (is_debug) {
_ = debug_allocator.deinit();
};
}
3 Likes
While I get that this works it does feel quite messy. Someonething I have tried is to write a thin wrapper around smp allocator myself which does work nicely. Its really just a struct eith noop methods and allocator() returning smp allocator.
Yeah I usually wrap main allocator in struct like this:
const AppAllocator = struct {
pub const is_debug = blk: {
if (builtin.os.tag == .wasi) break :blk false;
break :blk switch (builtin.mode) {
.Debug, .ReleaseSafe => true,
.ReleaseFast, .ReleaseSmall => false,
};
};
debug_allocator: if (is_debug) std.heap.DebugAllocator(.{}) else void,
pub const init: @This() = switch (is_debug) {
true => .{ .debug_allocator = .init },
false => .{ .debug_allocator = {} },
};
pub fn allocator(self: *@This()) std.mem.Allocator {
if (builtin.os.tag == .wasi) return std.heap.wasm_allocator;
return switch (is_debug) {
true => self.debug_allocator.allocator(),
false => std.heap.smp_allocator,
};
}
pub fn deinit(self: *@This()) void {
if (is_debug) _ = self.debug_allocator.deinit();
self.* = undefined;
}
};