Workspace


I made a Red-Black Tree in Zig because I couldn’t find one on the internet.

repo

Why pointer-free?

I didn’t start with the idea of making it pointer-free. I just didn’t want to allocate memory per node. So I tried to minimize heap usage — and this structure came out. It turned out to have some nice side-effects:

How I made it pointer-free

I used two key Zig features:

    pub const Idx = enum(usize) {
        sentinel = std.math.maxInt(usize),
        _,
    }
This gives me a type-safe way to represent indices — like `Idx` instead of plain `usize`.
I originally used it just for type safety, but Zig lets me add a sentinel (`Idx.sentinel`) with zero runtime overhead.
I think I saw a similar pattern in Zig’s own compiler code.

Memory reuse (and a problem I ran into)

At first, I didn’t worry too much about memory reuse. But once deletion worked, I realized freeing individual nodes would mean updating every index pointing to them. That’s messy, error-prone, and slow.

So I went with a freelist strategy. Instead of actually freeing nodes, I keep them in a linked list of reusable indices. In pointer-based implementations, you’d usually reinterpret the memory of a freed node to store the next pointer. But since I’m not using pointers, I reused an existing field to store the next index.


    pub const Node = struct {
        // other fields
        parent: Idx = .sentinel,

        pub const Idx = enum(usize) {
            // reuse .parent to store the next index in the freelist
            pub fn getNext(self: Idx, tree: *const Tree) Idx {
                const slice = tree.nodes.slice();
                return slice.items(.parent)[@intFromEnum(self)];
            }

            pub fn setNext(self: Idx, next: Idx, tree: *const Tree) void {
                const slice = tree.nodes.slice();
                slice.items(.parent)[@intFromEnum(self)] = next;
            }

            pub fn isFreeSpace(self: Idx) bool {
                return self != .sentinel;
            }
        };
    };

    fn recycleNode(tree: *Self, node: Node.Idx) void {
        node.setNext(tree.free_list, tree);
        tree.free_list = node;
    }

Final thoughts

It was a great way to apply some techniques I’d always wanted to try.

Data structures are hard. Unless you’re using Zig.