Allocator swapping and type consistency

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;
    }
};