Skip to content

Several types in one list

This is where Paternitas helps most.

One std list carries a Message and a Job.

Each comes back as itself.


The types

Each struct has a TypedNode, and its helper right after it.

const Message: type = struct {
    text: []const u8,
    tnode: paternitas.DoublyTypedNode = .{},
};
const TypedMessage: type = paternitas.Typed(Message);

const Job: type = struct {
    tnode: paternitas.DoublyTypedNode = .{},
    id: u32,
    attempts: u32 = 0,
};
const TypedJob: type = paternitas.Typed(Job);

The program

Call setTypeId on each struct. Put both in one list. Pop them.

var message: Message = .{ .text = "hello" };
TypedMessage.setTypeId(&message);

var job: Job = .{ .id = 42 };
TypedJob.setTypeId(&job);

var list: std.DoublyLinkedList = .{};
list.append(TypedMessage.node(&message));
list.append(TypedJob.node(&job));

const last: *std.DoublyLinkedList.Node = list.last orelse return error.ListEmpty;
if (!TypedJob.is(last)) return error.WrongParent;

var counts: Counts = .{};
while (list.popFirst()) |node| try recoverAndCount(node, &counts);

It logs one line per struct:

message: hello
job: 42

The full program: Two types, one list.


Ask each type in turn

One function handles one Node.

/// Asks each Parent type in turn. A Node that no type claims is an error.
fn recoverAndCount(node: *std.DoublyLinkedList.Node, counts: *Counts) !void {
    if (TypedMessage.parentFromNode(node)) |m| {
        std.log.info("message: {s}", .{m.*.text});
        counts.*.messages += 1;
        return;
    }
    if (TypedJob.parentFromNode(node)) |j| {
        std.log.info("job: {d}", .{j.*.id});
        counts.*.jobs += 1;
        return;
    }
    return error.UnknownParent;
}
  • The wrong type gets null, not a wrong pointer.
  • So you can ask Message first, then Job.
  • A Node that no type claims is an error you can see: error.UnknownParent.

When you only need to know

is(node) answers yes or no.

It does not give you the struct.

const last: *std.DoublyLinkedList.Node = list.last orelse return error.ListEmpty;
if (!TypedJob.is(last)) return error.WrongParent;

is gives false when setTypeId was never called on that struct.


Who knows the types

The loop knows the types it handles.

The list does not.

  • The list is still the plain std list.
  • Its calls do not change.
  • Add a third struct type: the list code stays the same.

In a large system, that matters.

Timeout list and a queue does the same for a server:

  • A server keeps its connections in a timeout list.
  • One connection leaves the list, goes through a queue, and comes back.