How to iterate over struct declarations?

Hai, ive been fiddling around lately with zig and have encountered some perplexing behaviour. My intention was to iterate through the functions of K, but I could not do that. Is that at all possible?

const std = @import("std");
// just a plain struct
const K = struct {
    field:u1 = 0,
    fn k(self: *const @This()) void { std.debug.print("YO {}", .{self.field}); }
};
pub fn main() void {
    std.debug.print("{}", .{@hasDecl(K, "k")}); // this shows true
    // std.fmt.comptimePrint("{any}", .{@typeInfo(K).Struct.decls}); // (1) this doesn't work (but should?)
    // @compileLog(@typeInfo(K).Struct.decls); // (2) this shows 0 items, but there should be 1, right?
}

@typeInfo is only showing public declaration. This is intended behavior
@hasDecl checks if the declaration exists and is accessible from the current scope.

If you want to iterate through functions of K it seems like your only option is to make the relevant functions public, or iterate through them in another way(for example if you have numbered names you can easily iterate through them with @hasDecl).

fmt.comptimePrint is intended to print to a comptime string. The naming might be a bit confusing, but this is in line with fmt.bufPrint printing to a supplied buffer and fmt.allocPrint printing to an allocated buffer.
@compileLog is the only intended way to print stuff at compile time.

2 Likes

@IntegratedQuantum is correct about the decl/field visibility issue (I believe this had to do with the @import functionality… can’t find the git issue anymore) - here’s a way to turn declarations into comptime fields so you can iterate over them:

const std = @import("std");

fn fooImpl(x: usize) usize { return x + 1; }
fn barImpl(x: usize) usize { return x - 1; }

const DeclsStruct = struct {
    foo: @TypeOf(fooImpl) = fooImpl,
    bar: @TypeOf(barImpl) = barImpl,
    baz: comptime_int = 42,
};

fn countFunctionFields(comptime T: type) usize {
    comptime var count = 0;
    for (@typeInfo(T).Struct.fields) |field| {
        if (@typeInfo(field.type) == .Fn) {
            count += 1;
        }
    }
    return count;
}

const Composite = struct {
    decls: DeclsStruct = .{},
    // other stuff...
};

pub fn main() !void {

    const func_fields = comptime countFunctionFields(DeclsStruct);

    std.debug.print("\nNumber of function fields: {}\n", .{ func_fields });
    
    const comp = Composite{ };
    std.debug.print("\n{}\n", .{ comp.decls.foo(42) });
    std.debug.print("\n{}\n", .{ comp.decls.bar(42) });
    std.debug.print("\n{}\n", .{ comp.decls.baz });
}

Now that we have wrapped foo and bar with the DeclsStruct, you’ll notice that the count is now two for detecting members that are functions (please note, these are not function pointers - they are compile time objects). Infact, all declarations can be converted to comptime fields.

If it’s important for you to be able to iterate over decls, you can use this path to manufacture an object that enables you to do this. It’s not directly native, but it’s not far off either.

@zigitta-the-famous, also - welcome to the forum. I’m going to edit the title title of this thread so it’s easier to search for future users.

1 Like