const std = @import("std"); /// Deinitializes `resource` (allocator owner) only if it is still live. /// /// Pass a pointer to the allocator owner, such as an `ArenaAllocator`. /// /// If `live` is `true`, calls `resource.deinit()` and then sets /// `live` to `false`, 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 deinitIfLive(resource: anytype, live: *bool) ?LiveDeinitReturn(@TypeOf(resource)) { if (!live.*) return null; 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) ?ExistsDeinitReturn(@TypeOf(resource)) { if (resource.*) |*payload| { const result = payload.deinit(); resource.* = null; return result; } return null; } // fn Payload(comptime Pointer: type) type { const Child = ValidatedChild(Pointer); if (@typeInfo(Child) != .optional) @compileError("Expected a pointer to an optional resource (*?T)!"); return @typeInfo(Child).optional.child; } fn ValidatedChild(comptime Pointer: type) type { if (@typeInfo(Pointer) != .pointer) @compileError("Expected a pointer!"); if (@typeInfo(Pointer).pointer.size != .one) @compileError("Expected a single-item pointer!"); return @typeInfo(Pointer).pointer.child; } fn LiveDeinitReturn(comptime Pointer: type) type { const Child = ValidatedChild(Pointer); return @typeInfo(@TypeOf(Child.deinit)).@"fn".return_type.?; } fn ExistsDeinitReturn(comptime Pointer: type) type { return @typeInfo(@TypeOf(Payload(Pointer).deinit)).@"fn".return_type.?; } // 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); }