Indexing a field at compiletime?

I have this packed struct, which is part of another packed struct.
That is because the size must be as small as possible.

    pub const Letters = packed struct
    {
        l0: u6,
        l1: u6,
        l2: u6,
        l3: u6,
        l4: u6,
        l5: u6,
        l6: u6,
    };

I was wondering if instead of this:

fn set(self: *Letters idx: u3, value: u6) void
{
     switch (idx)
    {
        0 => self.l0 = value, // etc
    }
}

I can use some supersmart @builtin function to directly set a value by index?

You can use @field and comptimePrint to achieve this:

@field(self, std.fmt.comptimePrint("l{}", .{idx})) = value;

Another alternative would be to use an enum for idx:

const Index = enum (u3) {
    l0 = 0,
    l1 = 1,
    ...
};
...
@field(self, @tagName(idx)) = value;
1 Like

Aha how advanced. I never saw comptimePrint, but for that idx needs to be comptime!

You can make idx comptime using switch and inline else:

switch(idx) {
    inline else => |comptime_idx| {
        ...
    },
}

This basically auto-generates one case per possible value.

2 Likes

You can use std.meta.fieldNames and a switch with inline else to convert the runtime index to a comptime index:

const std = @import("std");

pub const Letters = packed struct {
    l0: u6,
    l1: u6,
    l2: u6,
    l3: u6,
    l4: u6,
    l5: u6,
    l6: u6,

    fn set(self: *Letters, idx: u3, value: u6) void {
        const fields = comptime std.meta.fieldNames(Letters);
        switch (idx) {
            7 => @panic("out of bounds"),
            inline else => |i| @field(self, fields[i]) = value,
        }
    }
};

pub fn main() !void {
    var letters: Letters = .{
        .l0 = 0,
        .l1 = 0,
        .l2 = 0,
        .l3 = 0,
        .l4 = 0,
        .l5 = 0,
        .l6 = 0,
    };

    letters.set(2, 5);
}
3 Likes

Ok thanks! That works great. I was wondering if the “normal” switch generates the same code. Because this case is so small, would it matter?