One of the things I love about Zig is the comptime meta-programming that I can do.
In my case, I am going through a structure and finding all the structs with a particular structure and creating an enum and an array of function pointers that allow me to access those at compile time. (In the actual code, this comes from several @imports.) This particular way of doing things is working around 2 zig bugs (dependency loops with. simple pointers, and compile-time objects containing pointer fields). Here is code that is extracted and heavily simplified from the actual system.
Note that without the test this code works in my system. The only thing I can see is that the references in that code to Enum and functions are at compile time, but when I put a reference in a test, it moves them to runtime.
const std = @import("std");
const Tf = struct {
tfn: ThreadedFn.Fn,
};
pub const ThreadedFn = struct {
f: Fn,
pub const Fn = *const fn (foo: *Tf) u64;
};
const structures = .{
struct {
pub const bar = struct {
pub fn threadedFn(foo: *Tf) u64 {
return @intFromPtr(foo);
}
};
},
};
//comptime {@compileLog(structures[6].asThunk);}
fn declsCount() usize {
comptime var count = 0;
for (structures) |structure| {
count += @typeInfo(structure).@"struct".decls.len;
}
return count;
}
const EnumSort = struct {
field: *const std.builtin.Type.Declaration,
threadedFn: ThreadedFn.Fn,
};
const enumAndFunctions =
blk: {
@setEvalBranchQuota(100000);
var array: [declsCount()]EnumSort = undefined;
var n = 0;
for (structures) |structure| {
const decls = @typeInfo(structure).@"struct".decls;
for (decls) |decl| {
const ds = @field(structure, decl.name);
switch (@typeInfo(@TypeOf(ds))) {
.comptime_int, .int, .@"fn", .array => {},
else => {
if (@hasDecl(ds, "threadedFn")) {
array[n] = .{ .field = &decl, .threadedFn = @field(ds, "threadedFn") };
n += 1;
}
},
}
}
}
const enums = array[0..n];
var fields = @typeInfo(enum {}).@"enum".fields;
for (enums, 0..) |d, i| {
fields = fields ++ [_]std.builtin.Type.EnumField{.{
.name = d.field.name,
.value = i,
}};
}
const arraySize = enums.len;
var arrayFns: [arraySize]ThreadedFn.Fn = undefined;
var arrayNames: [arraySize][]u8 = undefined;
for (enums, 0..) |eb, index| {
arrayFns[index] = eb.threadedFn;
arrayNames[index] = eb.field.name;
}
break :blk .{ @Type(.{ .@"enum" = .{
.tag_type = usize,
.is_exhaustive = false,
.fields = fields,
.decls = &.{},
} }), arrayFns, arrayNames };
};
pub const Enum = enumAndFunctions[0];
const functions = enumAndFunctions[1];
pub const names = enumAndFunctions[2];
test "print threadedFns" {
std.debug.print("Threaded Functions:\n", .{});
for (names) |name| {
std.debug.print("{s:<25}", .{name});
}
// originally had this, but introduced names array in attempt to resolve depndency loop
// for (@typeInfo(Enum).@"enum".fields) |field| {
// std.debug.print("{s:<25}", .{field.name});
// }
std.debug.print("\n", .{});
// std.debug.print("git version: {s}\n", .{ config.git_version })
}
(note, the names field was added later, trying to work around the dependency loop.)