From 0a7ad2662cd6567be0ba5f0223b285d50daacd3e Mon Sep 17 00:00:00 2001 From: Overlord Date: Sun, 2 Aug 2026 23:41:15 +0200 Subject: [PATCH] Introduce `deinitIfExists` utility and replace `deinitIfLive` for optional resource cleanup in `main.zig`. --- src/main.zig | 9 ++--- src/memory/util.zig | 95 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/src/main.zig b/src/main.zig index 123bb1b..729ad99 100644 --- a/src/main.zig +++ b/src/main.zig @@ -28,11 +28,10 @@ pub fn main(init: std.process.Init) !void const allocator = if (debug) gpa.allocator() else std.heap.smp_allocator; - var tmp_arena = std.heap.ArenaAllocator.init(allocator); - var arena_live = true; - errdefer _ = zocket.memory.deinitIfLive(&tmp_arena, &arena_live); + var tmp_arena: ?std.heap.ArenaAllocator = .init(allocator); + errdefer _ = zocket.memory.deinitIfExists(&tmp_arena); - const tmp_allocator = tmp_arena.allocator(); + const tmp_allocator = tmp_arena.?.allocator(); var parser = try args.ArgumentParser.init(tmp_allocator, .{ .name = "zocket", @@ -61,7 +60,7 @@ pub fn main(init: std.process.Init) !void break :path try allocator.dupe(u8, tmp_path); }; - _ = zocket.memory.deinitIfLive(&tmp_arena, &arena_live); + _ = zocket.memory.deinitIfExists(&tmp_arena); try run(io, allocator, path); } diff --git a/src/memory/util.zig b/src/memory/util.zig index 7246854..5ec8266 100644 --- a/src/memory/util.zig +++ b/src/memory/util.zig @@ -1,23 +1,25 @@ const std = @import("std"); -/// Deinitializes `resource` only if it is still live. +/// Deinitializes `resource` (allocator owner) only if it is still live. /// -/// Pass a pointer to the original allocator owner, -/// such as an `ArenaAllocator`. +/// Pass a pointer to the allocator owner, such as an `ArenaAllocator`. /// -/// If `live` is `true`, calls `allocator.deinit()` and then sets +/// If `live` is `true`, calls `resource.deinit()` and then sets /// `live` to `false`, preventing a later cleanup path from attempting -/// to deinitialize the same allocator again. +/// to deinitialize the same resource again. /// -/// The return value of `deinit()` is returned from the function. +/// The return value of `resource.deinit()` is returned from the function. pub fn deinitIfLive(resource: anytype, live: *bool) ?@TypeOf(resource.deinit()) { comptime { - const T = @TypeOf(resource); + const Pointer = @TypeOf(resource); - if (@typeInfo(T) != .pointer) @compileError("deinitIfLive expects a pointer!"); - if (T == std.mem.Allocator) @compileError("deinitIfLive expects a pointer to an allocator owner, not std.mem.Allocator!"); + if (@typeInfo(Pointer) != .pointer) + @compileError("deinitIfLive expects a pointer!"); + + if (@typeInfo(Pointer).pointer.size != .one) + @compileError("deinitIfLive expects a single-item pointer!"); } if (!live.*) return null; @@ -25,3 +27,78 @@ pub fn deinitIfLive(resource: anytype, live: *bool) ?@TypeOf(resource.deinit()) live.* = false; return resource.deinit(); } + +/// Deinitializes the resources stored in `resource` +/// (allocator owner) only if still exists. +/// +/// Pass a pointer to an optional allocator owner, such as +/// `*?std.heap.ArenaAllocator`. +/// +/// If `resource.*` is non-null, calls `deinit()` on the payload +/// and then sets `resource.* = null`, preventing a later cleanup +/// path from attempting to deinitialize the same resource again. +/// +/// The return value of `resource.*.?.deinit()` is returned +/// from the function. +pub fn deinitIfExists(resource: anytype) ?DeinitReturn(@TypeOf(resource)) +{ + if (resource.*) |*payload| + { + const result = payload.deinit(); + resource.* = null; + return result; + } + + return null; +} + +// + +fn DeinitReturn(comptime Pointer: type) type { + return @typeInfo(@TypeOf(Payload(Pointer).deinit)).@"fn".return_type.?; +} + +fn Payload(comptime Pointer: type) type +{ + if (@typeInfo(Pointer) != .pointer) + @compileError("deinitIfExists expects a pointer!"); + + if (@typeInfo(Pointer).pointer.size != .one) + @compileError("deinitIfExists expects a single-item pointer!"); + + const Child = @typeInfo(Pointer).pointer.child; + + if (@typeInfo(Child) != .optional) + @compileError("deinitIfExists expects a pointer to an optional resource (*?T)!"); + + return @typeInfo(Child).optional.child; +} + +// + +test "deinitIfLive deinitializes an ArenaAllocator once" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var live = true; + + const alloc = arena.allocator(); + _ = try alloc.alloc(u8, 1); + + _ = deinitIfLive(&arena, &live); + try std.testing.expect(!live); + + // Must not deinitialize it again. + try std.testing.expect(deinitIfLive(&arena, &live) == null); +} + +test "deinitIfExists deinitializes an optional ArenaAllocator once" { + var arena: ?std.heap.ArenaAllocator = .init(std.testing.allocator); + + const alloc = arena.?.allocator(); + _ = try alloc.alloc(u8, 1); + + _ = deinitIfExists(&arena); + try std.testing.expect(arena == null); + + // Must not deinitialize it again. + try std.testing.expect(deinitIfExists(&arena) == null); +}