Generic struct fields

Is there a way to have struct fields with generic type. As in the field can have different values for different variables. And for this each I dont want the typical fn(comptime T:type) type. I want it like

const Foo: type = struct {
    baz: []Bar,
    
    const Bar: type = struct{
          gen_field
    };
};

Yes, it is possible to have generic fields during compile time. At runtime a specific type is required.

fn BarType(comptime T: type) type {
    return struct {
        gen_field: T
    };
}

const Foo: type = struct {
    baz: []Bar,
    const Bar: type = BarType(u32);
};

I understand what you are saying, but it is not what my initial question was about. With the code segment you provided (which is 100% correct and logical) baz will be just a slice of u32. What I asked what can you have a a generic array field where each field has a different type. That is say baz is a slice of Bar, baz[0] will be a type of Bar as a u8, baz[1] will be Bar as usize, baz[2] will be Bar as [:0]const u8 etc. Sorry if my initial question did not make this distinction.

Arrays always contain one element type, that type could be a union but it still is one type. If you want multiple different types you could build/use a tuple at comptime.

1 Like

How do you declare an array of tuple?

Follow up question. Can I declare the tuple in a way such that each of the array elements have two common fields?

Let’s say that your generic field needs to be either an integer or a floating point number.
You can have a union where only one of the fields is active. Of course you can add more fields.

const Foo: type = struct {
    baz: []Bar,
    
    const Bar: type = union {
          int_field: u32,
          float_field: f64,
    };
};

See the documentation for tagged unions: Documentation - The Zig Programming Language

You could do that, but I don’t think that is what you actually want. Instead you would use an tuple instead of an array.
However it is likely that you are better of doing what @dimdin suggested, using a union, probably should start with a tagged union union(enum) and only switch to a bare union if you are sure that that is what you want.

I don’t understand, what do you want?

I think it may be helpful if you provide more context about the kind of code that you want to write.
A short example with 2 different instantiations of how you want to use it.
Basically how would the code look without anything generic and then the parts you want to make generic. If you provide that it is far easier to come up with solutions.

I have a hard time imagining a scenario from your initial pseudo code.

Do you want to use this at comptime or instantiate it for different types for use at runtime?

1 Like

Sorry for the late response, I was busy with my school exams. But what I want is to make a schemaless graph data structure where each node can be a different type without using unions. I don’t want to use unions because that will mean I have to declare the union somewhere else in my code.

Reminds me of this topic:

So you are suggesting tuples should be the way to go for my use case?

Maybe, I would suggest unions, but you don’t want unions, then tuples are pretty much the only thing left, that or type erased pointers.

Do you want to build your graph at runtime or comptime?

That doesn’t seem like a big/strong reason to avoid unions.

I am using unions for a schemaed version but I was looking for a schemaless version

This is the full code of the schemaed graph data structure:

const std = @import("std");
const Type = std.builtin.Type;

var prng = std.rand.DefaultPrng.init(@as(u64, 468_357_246_135));
const rand = prng.random();

const Tag: type = [24]u8;
pub fn generate() Tag {
    const chars = "abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    var tag: Tag = undefined;
    var i: usize = 1;

    while (i < tag.len) : (i += 1) {
        const j = rand.uintLessThan(usize, chars.len);
        tag[i] = chars[j];
    }

    tag[0] = '#';
    tag[8] = '-';
    tag[16] = '-';

    return tag;
}

pub fn Graph(comptime T: type) type {
    if (@typeInfo(T) != .Union)
        @compileError("Invalid Schema");
    return struct {
        nodes: std.ArrayList(*Node),
        arena: std.heap.ArenaAllocator,

        const Self = @This();

        pub const Node: type = @Type(Type{ .Union = .{
            .decls = &[_]Type.Declaration{},
            .layout = .Auto,
            .tag_type = @typeInfo(T).Union.tag_type,
            .fields = blk: {
                var union_fields: [std.meta.fields(T).len]Type.UnionField = undefined;
                for (std.meta.fields(T), 0..) |union_field, i| {
                    const struct_fields = std.meta.fields(union_field.type);
                    const tag_field = [_]Type.StructField{.{ .type = Tag, .name = "tag", .alignment = 0, .is_comptime = false, .default_value = null }};
                    const edges_field = [_]Type.StructField{.{ .type = std.ArrayList(Edge), .name = "edges", .alignment = 0, .is_comptime = false, .default_value = null }};
                    const node_struct = @Type(Type{ .Struct = .{
                        .fields = tag_field ++ struct_fields ++ edges_field,
                        .layout = .Auto,
                        .decls = &[_]Type.Declaration{},
                        .is_tuple = false,
                    } });
                    union_fields[i] = Type.UnionField{
                        .alignment = union_field.alignment,
                        .name = union_field.name,
                        .type = node_struct,
                    };
                }

                break :blk &union_fields;
            },
        } });

        pub const Edge: type = struct {
            label: []const u8,
            nodes: std.ArrayList(*Node),
        };

        pub fn init(alloc: std.mem.Allocator) !*Self {
            var self = try alloc.create(Self);
            self.arena = std.heap.ArenaAllocator.init(alloc);
            self.nodes = std.ArrayList(*Node).init(self.arena.allocator());

            return self;
        }

        pub fn deinit(self: *Self) void {
            for (self.nodes.items) |value| {}

            self.nodes.deinit();
            self.arena.allocator().destroy(self);
        }

        pub fn addNode(self: *Self, comptime data: T) Tag {
            const union_tag: [:0]const u8 = @tagName(data);
            const field_type = std.meta.TagPayloadByName(Node, union_tag);
            var node: field_type = undefined;
            node.tag = generate();
            node.edges = std.ArrayList(Edge).init(self.arena.allocator());
            const data_field = @field(data, union_tag);

            inline for (std.meta.fields(@TypeOf(data_field))) |field| {
                @field(node, field.name) = @field(data_field, field.name);
            }

            self.nodes.append(@constCast(&@unionInit(Node, union_tag, node))) catch unreachable;
            return node.tag;
        }
    };
}

test "Graph Nodes" {
    const Test: type = union(enum) {
        Foo: struct {
            field1: usize,
            field2: usize,
            field3: usize,
            field4: usize,
        },
        Bar: struct {
            field1: usize,
            field2: usize,
            field3: usize,
            field4: usize,
        },
        Baz: struct {
            field1: usize,
            field2: usize,
            field3: usize,
            field4: usize,
        },
    };
    const node_fields: []const Type.UnionField = std.meta.fields(Graph(Test).Node);
    const test_fields: []const Type.UnionField = std.meta.fields(Test);
    try std.testing.expect(node_fields.len == test_fields.len);
    try std.testing.expect(std.meta.fields(node_fields[0].type).len - 2 == std.meta.fields(test_fields[0].type).len);
    try std.testing.expect(std.meta.fields(node_fields[1].type).len - 2 == std.meta.fields(test_fields[1].type).len);
    try std.testing.expect(std.meta.fields(node_fields[2].type).len - 2 == std.meta.fields(test_fields[2].type).len);
}

test "addNode" {
    const Test: type = union(enum) {
        Foo: struct {
            field1: usize,
            field2: usize,
            field3: usize,
            field4: usize,
        },
        Bar: struct {
            field1: usize,
            field2: usize,
            field3: usize,
            field4: usize,
        },
        Baz: struct {
            field1: usize,
            field2: usize,
            field3: usize,
            field4: usize,
        },
    };

    const graph = try Graph(Test).init(std.testing.allocator);
    defer graph.deinit();

    const ini_len = graph.nodes.items.len;
    const tag = graph.addNode(.{ .Foo = .{
        .field3 = 4,
        .field1 = 7,
        .field4 = 9,
        .field2 = 17,
    } });
    const fin_len = graph.nodes.items.len;

    try std.testing.expect(@TypeOf(tag) == Tag);
    try std.testing.expect(fin_len > ini_len);
    try std.testing.expect(fin_len - ini_len == 1);
}