Skip to content

API Reference — PolyHelper

Want the manual walkthrough first? See PolyNode, ItemHandle, Slot.

PolyHelper generates the tag, check, identification functions, and init for any PolyNode type.
One call replaces all the manual boilerplate.

pub fn PolyHelper(comptime T: type) type
  • T must have a field poly: PolyNode. Compile error otherwise.
  • Returns a namespace with four members.

What PolyHelper generates

pub const TAG: *const anyopaque
  • Unique runtime address for type T.
  • Same as the manual var _tag: PolyTag = .{}; const TAG = &_tag; pattern.
pub fn isIt(tag: *const anyopaque) bool
  • Returns tag == TAG.
  • Same as the manual poly.tag == EVENT_TAG check.
pub fn identifyNodeAs(node: *PolyNode) ?*T
  • Returns null if the runtime tag does not match.
  • Returns @fieldParentPtr("poly", node) if it does.
  • For infrastructure code that works with *PolyNode directly (mailbox, pool, list walks).
pub fn mustIdentifyNodeAs(node: *PolyNode) *T
  • Same as identifyNodeAs, but panics (orelse unreachable) if the tag does not match.
pub fn identifySlotAs(slot: *const Slot) ?*T
  • Returns null if the Slot is empty or the tag does not match.
  • For application code that works with Slots (examples, tests, stories).
pub fn mustIdentifySlotAs(slot: *const Slot) *T
  • Same as identifySlotAs, but panics if the Slot is empty or the tag does not match.
pub fn init(self: *T) void
  • Sets self.poly = .{ .node = .{}, .tag = TAG }.
  • Same as the manual init in Step 3.

Usage

pub const Event = struct {
    poly: PolyNode,
    code: i32,
};

pub const EventPolyHelper = polynode.PolyHelper(Event);

Naming convention: XxxPolyHelper = polynode.PolyHelper(Xxx).

The same example, now with PolyHelper

// Create and initialize (Step 3 is now one call)
var ev: Event = .{ .code = 42 };
EventPolyHelper.init(&ev);

// Get PolyNode pointer (same as before)
const poly: *PolyNode = &ev.poly;

// Identify and recover (Steps 5+6 combined, returns null on wrong tag)
const recovered: *Event = EventPolyHelper.mustIdentifyNodeAs(poly);
// recovered.code == 42
Manual                              With PolyHelper

var _event_tag: PolyTag = .{};      (generated inside PolyHelper)
const EVENT_TAG = &_event_tag;      EventPolyHelper.TAG

poly.tag == EVENT_TAG               EventPolyHelper.isIt(poly.tag)

if (poly.tag == EVENT_TAG)          EventPolyHelper.identifyNodeAs(poly)
  @fieldParentPtr("poly", poly)       → ?*Event (null if wrong tag)

// slot: ?*PolyNode                 EventPolyHelper.identifySlotAs(&slot)
                                       → ?*Event (null if slot empty or wrong tag)

ev.poly = .{.node=.{},.tag=TAG};    EventPolyHelper.init(&ev)

Same operations. Same runtime cost. Less boilerplate. Compile-time validation.

See helpers/types.zig for the pattern.

PolyHelper — create and destroy

These two functions exist only when T does not declare no_create_destroy.
They collapse the three-step alloc+init+slot pattern into one call.

pub fn create(allocator: std.mem.Allocator, slot: *Slot) !void
  • Asserts slot.* == null.
  • Allocates T.
  • Zero-initializes.
  • Calls init.
  • Sets slot.* to point to the new node.
pub fn destroy(allocator: std.mem.Allocator, slot: *Slot) void
  • If slot.* == null: returns immediately (no-op).
  • Asserts node is not linked.
  • Sets slot.* to null before freeing — prevents use-after-free.
  • Frees the memory.

Old pattern vs new

Old (manual):                        New (PolyHelper.create):

  const ev = try alloc.create(T);     try EventPolyHelper.create(alloc, &slot);
  ev.* = .{};
  EventPolyHelper.init(ev);
  slot.* = &ev.poly;
Old (manual):                        New (PolyHelper.destroy):

  alloc.destroy(                       EventPolyHelper.destroy(alloc, &slot);
    EventPolyHelper.mustIdentifySlotAs(&slot));  // null-safe, clears slot
  slot.* = null;

comptime selection — no_create_destroy

Some types must not expose create/destroy.

const no_create_destroy = void{};

If T declares this field, PolyHelper(T) generates only: TAG, isIt, identifyNodeAs, mustIdentifyNodeAs, identifySlotAs, mustIdentifySlotAs, init.

Infrastructure types (_Mailbox, _Pool) declare no_create_destroy.
They manage their own lifecycle.
Generating create/destroy for them would be wrong.

PolyHelper(T)
  ├── @hasDecl(T, "no_create_destroy") == false
  │     → TAG, isIt, identifyNodeAs, mustIdentifyNodeAs, identifySlotAs, mustIdentifySlotAs, init, create, destroy
  └── @hasDecl(T, "no_create_destroy") == true
        → TAG, isIt, identifyNodeAs, mustIdentifyNodeAs, identifySlotAs, mustIdentifySlotAs, init

Next: API Reference — Mailbox.