const coral = @import("./coral.zig"); const std = @import("std"); destroy: *const fn (*anyopaque) void, erased: *anyopaque, fn Layout(comptime Type: type) type { return switch (Type) { void => struct { fn destroy(_: *anyopaque) void {} }, else => struct { allocator: std.mem.Allocator, value: Type, const TypeLayout = @This(); fn destroy(erased: *anyopaque) void { const layout: *TypeLayout = @ptrCast(@alignCast(erased)); if (@hasDecl(Type, "deinit")) { const deinit_fn = switch (@typeInfo(@TypeOf(Type.deinit))) { .@"fn" => |@"fn"| @"fn", else => @compileError(std.fmt.comptimePrint("Declaration `{s}.deinit` must be an fn type to be boxable", .{ @typeName(Type), })), }; if (deinit_fn.params.len != 1 or deinit_fn.params[0].type != *Type) { @compileError(std.fmt.comptimePrint("Fn `{s}.deinit` is only permitted 1 parameter to be boxable and it must be of type {s}", .{ @typeName(Type), @typeName(*Type), })); } layout.value.deinit(); } layout.allocator.destroy(layout); } }, }; } const Self = @This(); pub fn deinit(self: *Self) void { self.destroy(self.erased); self.* = undefined; } pub fn has(self: Self, comptime Value: type) ?*Value { const ValueLayout = Layout(Value); if (self.destroy == ValueLayout.destroy) { const layout: *ValueLayout = @ptrCast(@alignCast(self.erased)); return &layout.value; } return null; } pub fn init(allocator: std.mem.Allocator, value: anytype) std.mem.Allocator.Error!Self { const Value = @TypeOf(value); const ValueLayout = Layout(Value); const value_layout = try allocator.create(ValueLayout); errdefer { allocator.destroy(value_layout); } if (@hasField(ValueLayout, "allocator")) { value_layout.allocator = allocator; } if (@hasField(ValueLayout, "value")) { value_layout.value = value; } return .{ .erased = @ptrCast(value_layout), .destroy = ValueLayout.destroy, }; }