Discovering all public variables of a container that implement some generic type

a different scenario from this post, where i was looking for a (function) decl of a known name…

in this case, i want to iterate through all of the decls within some container (which seems straightforward)… once i have the names of these decls, i then want to reflect on its type – which i’m not exactly sure how to do…

the tricky part is that i’m looking for (say) all variable decls that implement some generic type… for example, if i had some generic function F(T) how do i find all the decls whose type is an instance of this generic function:

fn F(T: type) type { ... }

var foo = F(u8){};

var bar = F(MyStructType){};

var not_interested = true;

given this container, how can i determine that both foo and bar are of types generated via F???

i’ll probably have follow-on questions about how to programmatically manipulate these discovered values at runtime, but that’s enough for now :wink:

You can do that only for the public declarations. std.builtin.Type.Struct is the result of @typeInfo for a struct used as a namespace. The .Struct typeinfo includes the fields and the pub decls (var and fn).

fn iterate(comptime T: type) void {
    inline for (@typeinfo(T).Struct.decls) |decl| {
        const name = decl.name;
    }
}

@field can access the field, the variable or the function by its name, in a namespace T

    const fld = @field(T, decl.name);
    if (@typeInfo(@TypeOf(fld)) == .Struct) {
        ...

again we can use @typeInfo to get metadata for the type of Type.


You cannot do it directly. For foo and bar their type is returned somehow from F. But what you can have is their name and the type information. Their type information is from the type returned by F. I am assuming that F always return a struct; you can add a dummy const F_derived to mark that this struct is constructed from F.

fn F(T: type) type {
    return struct {
        const F_derived = true;
        value: T,
    };
}

pub var foo = F(u8);

comptime {
    iterate(@Self());
}

fn iterate(comptime T: type) void {
    inline for (@typeinfo(T).Struct.decls) |decl| {
        const fld = @field(T, decl.name);
        const ti = @typeInfo(@TypeOf(fld));
        if (ti == .Struct and @hasDecl(fld, "F_derived")) {
            @compileLog(decl.name, ti);
        }
    }
}

@compileLog prints the value and the type of its arguments at compile time.

2 Likes

long story, but i’d actually like to access these variable at run-time – invoking some method inside the struct returned by F

i also discovered that the string returned @typeName may be sufficient to identify the variables of interest; i “know” which module defines F, etc…

though not my exact use case, suppose i wanted to store (serialize) the values of all matching variables to a file at run-time… i guess this not unlike what json stringify does – although i want to call methods on these variables (which are in fact structs)…

thoughts how to approach this???

@field gives you the field value:

const std = @import("std");

const X = struct {
    foo: u8,
    bar: u8,

    const Self = @This();

    fn save(self: Self) void {
        const info = @typeInfo(Self).Struct;
        inline for (info.fields) |field| {
            const name = field.name;
            const value = @field(self, name);
            std.debug.print("{s}={any}\n", .{ name, value });
        }
    }
};

pub fn main() void {
    const x = X{
        .foo = 1,
        .bar = 2,
    };
    x.save();
}

prints:

foo=1
bar=2

returning to my original problem, i want to:

  1. find all public decls in a container, whose type is a struct returned by my F(T);

  2. fetch the value of this decl (at runtime); and

  3. call some save method on this value, where save is defined in the struct returned by my F(T)

i believe i can achieve step 1 using techniques described earlier in the post…

i’m able to call @field as part of step 2, though it’s type is “unknown” (to me, anyway)

i have no clue how to perform step 3 at this point… i know i want to apply .save() to this struct value, but can’t figure out how to coerce it into the specific type i want…

let me create some sample code, so we have something to look at…

One way is to add save in the F returned struct.

const std = @import("std");

fn F(comptime T: type) type {
    return struct {
        value: T,

        const Self = @This();

        fn init(initial_value: T) Self {
            return .{
                .value = initial_value,
            };
        }

        fn save(self: Self) void {
            const info = @typeInfo(Self).Struct;
            inline for (info.fields) |field| {
                const name = field.name;
                const value = @field(self, name);
                std.debug.print("{s}={any}\n", .{ name, value });
            }
        }
    };
}

pub var foo = F(u8).init(42);

fn iterate(comptime T: type) void {
    inline for (@typeInfo(T).Struct.decls) |decl| {
        const fld = @field(T, decl.name);
        const FT = @TypeOf(fld);
        const ti = @typeInfo(FT);
        if (ti == .Struct and @hasDecl(FT, "save")) {
            std.debug.print("{s}\n", .{decl.name});
            fld.save();
        }
    }
}

pub fn main() void {
    iterate(@This());
}
1 Like

fantastic!!!

i just ran your snippet with the addition of pub var bar = F(bool).init(true)

incidently, a “better” way to find types returned by F(T) is to look at the @typeName of your FT constant… in your example, it will be "main.F(u8)"

in my application, the functor F is defined within some “known” module x.y.z which means any decl whose type name starts with x.y.z.F is one that i’m looking for…

i’m just 3 weeks into zig, and continue to be amazed by what i’m learning through this forum…

1 Like