Here’s a very interesting bit of Zig that I came across again this week: @fieldParentPtr. It’s a prism through which you can see a lot of Zig’s character. That’s not what the official docs say, of course. They say that @fieldParentPtr:
Given a pointer to a field, returns the base pointer of a struct.
That doesn’t paint a, uhm, full picture, I’d say. So here’s some code:
const Parent = struct {
name: []const u8,
left: Child,
right: Child,
};
const Child = struct {
name: []const u8,
position: enum {
left,
right,
},
fn parentName(self: *Child) []const u8 {
const parent = switch (self.position) {
.left => @fieldParentPtr(Parent, "left", self),
.right => @fieldParentPtr(Parent, "right", self),
};
return parent.name;
}
};Parent has two Children, all three have a name and the parentName method on Child returns its parent’s name.
The interesting bit is the invocation of @fieldParentPtr:
@fieldParentPtr(Parent, "left", self)What that says is: give me a pointer to Parent, based on the pointer of the "left" field on Parent that I’m giving you.
It’s doing pointer math for you!
What it does is take self, which is a pointer to a Child, look up the position of left field in Parent, then calculate how many bytes to subtract from left to get to the start of Parent, do that calculation and return a pointer to Parent.
Here’s roughly-equivalent C code to show what’s going on under the hood:
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
enum position {
LEFT,
RIGHT
};
struct child {
const char *name;
enum position position;
};
struct parent {
const char *name;
struct child left;
struct child right;
};
const char *parentName(const struct child *child) {
uintptr_t offset = 0;
switch (child->position) {
// 1. Turn a `0` into a `struct parent *` pointer
// 2. Access `->left` or `->right`, take pointer of that.
// 3. That increased the `0` address to the field address
// -> our offset
case LEFT:
offset = (uintptr_t) &(((struct parent *)0)->left);
break;
case RIGHT:
offset = (uintptr_t) &(((struct parent *)0)->right);
break;
}
// Now we can take that offset and get to the name
struct parent *parent = (struct parent *)((char *)child - offset);
return parent->name;
}
int main(void) {
struct parent parent = {
.name = "bob",
.left = { .name = "child1", .position = LEFT },
.right = { .name = "child2", .position = RIGHT },
};
assert(strcmp(parentName(&parent.left), "bob") == 0);
assert(strcmp(parentName(&parent.right), "bob") == 0);
printf("And bob's... not your uncle?\n");
return 0;
}What @fieldParentPtr does is what parentName does here, except it’s compile-time safe!
If I change the Zig code from
@fieldParentPtr(Parent, "left", self)to
@fieldParentPtr(Parent, "in emergency: break dance", self)it breaks!
field_parent_ptr_simple.zig:18:46: error: no field named 'in emergency: break dance' in struct 'f
ield_parent_ptr_simple.Parent'
.left => @fieldParentPtr(Parent, "in emergency: break dance", self),
^~~~~~~~~~~~~~~~~~~~~~~~~~~One could re-implement @fieldParentPtr in Zig without it being a compiler builtin. The fact it is a compiler builtin and that people use it to implement interfaces in Zig shows you what Zig is about.
It’s fun to learn.



It's a neat feature, tho not without dragons that made me looking for alternative ways to do interfaces in Zig: https://github.com/ziglang/zig/issues/591
I think zig does not guarantee the order of the fields in a struct, that’s why something like @filedParentPtr has to exist.