Register Spill

Share this post

Zig Zaggin'

registerspill.thorstenball.com

Discover more from Register Spill

Thoughts about software engineering I can't keep in my head. Too ephemeral for blog posts, too long for social media. It's the messages I'd sent if you'd asked me what's on my mind.
Over 1,000 subscribers
Continue reading
Sign in

Zig Zaggin'

Thorsten Ball
Nov 3, 2023
7
Share this post

Zig Zaggin'

registerspill.thorstenball.com
2
Share

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.

Thanks for reading! Subscribe here:

7
Share this post

Zig Zaggin'

registerspill.thorstenball.com
2
Share
2 Comments
Share this discussion

Zig Zaggin'

registerspill.thorstenball.com
Ruslan Prakapchuk
Nov 8

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

Expand full comment
Reply
Share
Andres
Nov 3

I think zig does not guarantee the order of the fields in a struct, that’s why something like @filedParentPtr has to exist.

Expand full comment
Reply
Share
Top
New
Community

No posts

Ready for more?

© 2023 Thorsten Ball
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing