Using Arena along with special deinit cleanup logic

I have a generic Arena type:

pub fn Arena(comptime T: type) type {
    return struct {
        arena: *std.heap.ArenaAllocator,
        value: T,
        pub fn deinit(self: @This()) void {
            if (@hasDecl(T, "deinit")) {
                self.value.deinit();
            }
            const allocator = self.arena.child_allocator;
            self.arena.deinit();
            allocator.destroy(self.arena);
        }
    };
}

I added the @hasDecl recently but I’m not sure if if its a good idea. Is there a better way to do that?

Here is example usage, I have a struct that interacts with some C code that needs manual drops (hence the deinit):


pub const ZenohHandler = struct {
    config: *zenoh.c.z_owned_config_t,
    session: *zenoh.c.z_owned_session_t,
    pubs: std.StringArrayHashMap(zenoh.c.z_owned_publisher_t),

    pub fn init(p_allocator: std.mem.Allocator, eni: gcat.ENI, maybe_config_file: ?[:0]const u8) !gcat.Arena(ZenohHandler) {
        var arena = try p_allocator.create(std.heap.ArenaAllocator);
        arena.* = .init(p_allocator);
        errdefer p_allocator.destroy(arena);
        errdefer arena.deinit();
        const allocator = arena.allocator();

        const config = try allocator.create(zenoh.c.z_owned_config_t);
        if (maybe_config_file) |config_file| {
            try zenoh.err(zenoh.c.zc_config_from_file(config, config_file.ptr));
        } else {
            try zenoh.err(zenoh.c.z_config_default(config));
        }
        errdefer zenoh.drop(zenoh.move(config));

        var open_options: zenoh.c.z_open_options_t = undefined;
        zenoh.c.z_open_options_default(&open_options);

        const session = try allocator.create(zenoh.c.z_owned_session_t);
        const open_result = zenoh.c.z_open(session, zenoh.move(config), &open_options);
        try zenoh.err(open_result);
        errdefer zenoh.drop(zenoh.move(session));

        var pubs = std.StringArrayHashMap(zenoh.c.z_owned_publisher_t).init(allocator);
        errdefer pubs.deinit();
        errdefer {
            for (pubs.values()) |*publisher| {
                zenoh.drop(zenoh.move(publisher));
            }
        }

        for (eni.subdevices) |subdevice| {
            for (subdevice.inputs) |input| {
                for (input.entries) |entry| {
                    if (entry.pv_name == null) continue;
                    var publisher: zenoh.c.z_owned_publisher_t = undefined;
                    var view_keyexpr: zenoh.c.z_view_keyexpr_t = undefined;
                    std.log.warn("zenoh: declaring publisher: {s}, ethercat type: {s}", .{ entry.pv_name.?, @tagName(entry.type) });
                    const result = zenoh.c.z_view_keyexpr_from_str(&view_keyexpr, entry.pv_name.?.ptr);
                    try zenoh.err(result);
                    var publisher_options: zenoh.c.z_publisher_options_t = undefined;
                    zenoh.c.z_publisher_options_default(&publisher_options);
                    publisher_options.congestion_control = zenoh.c.Z_CONGESTION_CONTROL_DROP;
                    const result2 = zenoh.c.z_declare_publisher(zenoh.loan(session), &publisher, zenoh.loan(&view_keyexpr), &publisher_options);
                    try zenoh.err(result2);
                    const put_result = try pubs.getOrPutValue(entry.pv_name.?, publisher);
                    if (put_result.found_existing) return error.PVNameConflict; // TODO: assert this?
                }
            }
        }
        return gcat.Arena(ZenohHandler){ .arena = arena, .value = .{
            .config = config,
            .session = session,
            .pubs = pubs,
        } };
    }

    pub fn deinit(self: ZenohHandler) void {
        for (self.pubs.values()) |*publisher| {
            zenoh.drop(zenoh.move(publisher));
        }
        zenoh.drop(zenoh.move(self.config));
        zenoh.drop(zenoh.move(self.session));
    }
}

I am both allocating with the arena, and there are hidden allocations in the C dependency that I have to drop. I want to use an arena for the zig allocations, and still drop the C allocations.

Is there a better way than @hasDecl to address this issue? Maybe I should just store a pointer to an arena allocation in the struct and free that as part of deinit?

Also, side-note, isn’t this Arena type I have created a “managed” style type? It is modeled after std.json.

I got rid of the @hasDecl, way to smelly!! and just stored a pointer to an arena in the struct.

pub fn Arena(comptime T: type) type {
    return struct {
        arena: *std.heap.ArenaAllocator,
        value: T,
        pub fn deinit(self: @This()) void {
            const allocator = self.arena.child_allocator;
            self.arena.deinit();
            allocator.destroy(self.arena);
        }
    };
}

pub const ZenohHandler = struct {
    arena: *std.heap.ArenaAllocator,
    config: *zenoh.c.z_owned_config_t,
    session: *zenoh.c.z_owned_session_t,
    // TODO: store string keys as [:0] const u8 by calling hash map ourselves with StringContext
    pubs: std.StringArrayHashMap(zenoh.c.z_owned_publisher_t),

    pub fn init(p_allocator: std.mem.Allocator, eni: gcat.ENI, maybe_config_file: ?[:0]const u8) !ZenohHandler {
        var arena = try p_allocator.create(std.heap.ArenaAllocator);
        arena.* = .init(p_allocator);
        errdefer p_allocator.destroy(arena);
        errdefer arena.deinit();
        const allocator = arena.allocator();

        const config = try allocator.create(zenoh.c.z_owned_config_t);
        if (maybe_config_file) |config_file| {
            try zenoh.err(zenoh.c.zc_config_from_file(config, config_file.ptr));
        } else {
            try zenoh.err(zenoh.c.z_config_default(config));
        }
        errdefer zenoh.drop(zenoh.move(config));

        var open_options: zenoh.c.z_open_options_t = undefined;
        zenoh.c.z_open_options_default(&open_options);

        const session = try allocator.create(zenoh.c.z_owned_session_t);
        const open_result = zenoh.c.z_open(session, zenoh.move(config), &open_options);
        try zenoh.err(open_result);
        errdefer zenoh.drop(zenoh.move(session));

        var pubs = std.StringArrayHashMap(zenoh.c.z_owned_publisher_t).init(allocator);
        errdefer pubs.deinit();
        errdefer {
            for (pubs.values()) |*publisher| {
                zenoh.drop(zenoh.move(publisher));
            }
        }

        for (eni.subdevices) |subdevice| {
            for (subdevice.inputs) |input| {
                for (input.entries) |entry| {
                    if (entry.pv_name == null) continue;
                    var publisher: zenoh.c.z_owned_publisher_t = undefined;
                    var view_keyexpr: zenoh.c.z_view_keyexpr_t = undefined;
                    std.log.warn("zenoh: declaring publisher: {s}, ethercat type: {s}", .{ entry.pv_name.?, @tagName(entry.type) });
                    const result = zenoh.c.z_view_keyexpr_from_str(&view_keyexpr, entry.pv_name.?.ptr);
                    try zenoh.err(result);
                    var publisher_options: zenoh.c.z_publisher_options_t = undefined;
                    zenoh.c.z_publisher_options_default(&publisher_options);
                    publisher_options.congestion_control = zenoh.c.Z_CONGESTION_CONTROL_DROP;
                    const result2 = zenoh.c.z_declare_publisher(zenoh.loan(session), &publisher, zenoh.loan(&view_keyexpr), &publisher_options);
                    try zenoh.err(result2);
                    const put_result = try pubs.getOrPutValue(entry.pv_name.?, publisher);
                    if (put_result.found_existing) return error.PVNameConflict; // TODO: assert this?
                }
            }
        }
        return ZenohHandler{
            .arena = arena,
            .config = config,
            .session = session,
            .pubs = pubs,
        };
    }

    pub fn deinit(self: ZenohHandler, p_allocator: std.mem.Allocator) void {
        for (self.pubs.values()) |*publisher| {
            zenoh.drop(zenoh.move(publisher));
        }
        zenoh.drop(zenoh.move(self.config));
        zenoh.drop(zenoh.move(self.session));
        self.arena.deinit();
        p_allocator.destroy(self.arena);
    }

}