Your own container
Most users can skip this page.
It is for you when you write the container itself: a stack, a pool, a queue of mixed types.
When you need it
You may not need it.
- One std list of many struct types?
Typed(P)covers it. - A queue or a map? An
Anyin a std container covers it.
You need it when:
- your container keeps structs of many types, and does not know them;
- it needs no extra memory per item;
- or you pass a struct to code that knows nothing of Paternitas.
NAQ: Can I use my own list?
Yes. This page is about that.
Your list keeps *Anchors, and gets each struct back with a type check.
The Anchor
An Anchor is a one-word handle to a struct with a TypedNode.
- Every TypedNode holds one Anchor.
setTypeIdwrites the struct's type id into it.- You never make an Anchor. You only pass
*Anchor.
In and out:
| call | what it does |
|---|---|
TypedJob.anchor(&job) |
gives you the *Anchor of job |
TypedJob.parentFromAnchor(a) |
checks the type, and gives you *Job or null |
TypedJob.mustParentFromAnchor(a) |
the same, but panics for another type |
*Node carries any struct with the same Node kind.
*Anchor carries any struct.
Both turn back into your struct with a type check.
What anchor.info() gives you
anchor.info() gives the TypeInfo of the struct's type.
It gives null when setTypeId was never called.
| call | what you get |
|---|---|
info.nextField(a) |
a pointer-sized word to chain through: the Node's next field |
info.node(a, N) |
the std Node, as type N |
info.parent(a) |
the struct's address, with no type |
info.toAny(a) |
an Any |
info.node panics, in every build mode, when N is the wrong Node kind.
A stack of mixed types
This stack keeps *Anchors. It allocates nothing.
It chains them through each item's next field:
/// Returns the item's `next` field. This stack keeps a pointer to the next
/// `*Anchor` in it.
///
/// Returns `error.NoTypeId` when `setTypeId` was never called on the item.
fn chainWord(a: *paternitas.Anchor) !*?*paternitas.Anchor {
const ti: *const paternitas.container.TypeInfo = a.info() orelse return error.NoTypeId;
return @ptrCast(ti.nextField(a));
}
/// A last-in, first-out stack of `*Anchor`s. It allocates nothing.
const Stack: type = struct {
top: ?*paternitas.Anchor = null,
fn push(s: *Stack, a: *paternitas.Anchor) !void {
const word: *?*paternitas.Anchor = try chainWord(a);
word.* = s.*.top;
s.*.top = a;
}
fn pop(s: *Stack) !?*paternitas.Anchor {
const a: *paternitas.Anchor = s.*.top orelse return null;
const word: *?*paternitas.Anchor = try chainWord(a);
s.*.top = word.*;
word.* = null;
return a;
}
};
Push with anchor. Pop, and get the struct back with parentFromAnchor:
var stack: Stack = .{};
try stack.push(TypedMessage.anchor(&message));
try stack.push(TypedJob.anchor(&job));
const first: *paternitas.Anchor = try stack.pop() orelse return error.StackEmpty;
const j: *Job = TypedJob.parentFromAnchor(first) orelse return error.WrongOrder;
const second: *paternitas.Anchor = try stack.pop() orelse return error.StackEmpty;
const m: *Message = TypedMessage.parentFromAnchor(second) orelse return error.WrongOrder;
Message has a SinglyTypedNode. Job has a DoublyTypedNode.
One stack carries both.
The full program: Your own stack.
The rules
- Call
setTypeIdon each struct before it enters your container. - A std list uses the same
nextword. A struct MUST NOT be in your container and in a std list at the same time. - Your container owns the meaning of that word. Paternitas never reads or writes it.
- The struct MUST stay alive while your container keeps its Anchor.
- Paternitas locks nothing. Guard a shared container yourself.
The full set of calls is in the API docs, under Anchor and container.