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.
Tmust have a fieldpoly: PolyNode. Compile error otherwise.- Returns a namespace with four members.
What PolyHelper generates
- Unique runtime address for type
T. - Same as the manual
var _tag: PolyTag = .{}; const TAG = &_tag;pattern.
- Returns
tag == TAG. - Same as the manual
poly.tag == EVENT_TAGcheck.
- Returns
nullif the runtime tag does not match. - Returns
@fieldParentPtr("poly", node)if it does. - For infrastructure code that works with
*PolyNodedirectly (mailbox, pool, list walks).
- Same as
identifyNodeAs, but panics (orelse unreachable) if the tag does not match.
- Returns
nullif the Slot is empty or the tag does not match. - For application code that works with Slots (examples, tests, stories).
- Same as
identifySlotAs, but panics if the Slot is empty or the tag does not match.
- 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.
- Asserts
slot.* == null. - Allocates
T. - Zero-initializes.
- Calls
init. - Sets
slot.*to point to the new node.
- 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.
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.