Example of generating a struct from comptime information

I’m trying to generate a packed struct type from some rather complex comptime-known information:

const names: []const []const u8 = &.{ "foo", "bar" };

fn CreateStruct(comptime field_names: []const []const u8) type {
    // make this:
    //     packed struct {
    //         foo: bool,
    //         bar: bool,
    //     };

    return ...
}

I’m pretty sure I can do this using comptime concatenation of std.builtin.Type.StructField but I’m having some trouble figuring out how to do it, could someone provide an example based on what I have above?

https://github.com/ziglang/zig/blob/31f353cd925a565dc32fa013c3ff767a06963adc/lib/std/enums.zig#L15

I think this functions in std.enums is doing what you wish but with enums.

Just a minimal example that works, could obviously use some optimizations. if you are using 0.13, you will have to rename some of the enum fields to match that version.

fn CreateStruct(comptime field_names: []const [:0]const u8) type {
    var fields: [field_names.len]std.builtin.Type.StructField = undefined;
    for (0.., field_names) |i, field_name| {
        fields[i] = .{
            .name = field_name,
            .type = bool,
            .default_value_ptr = null,
            .is_comptime = false,
            .alignment = 0,
        };
    }

    return @Type(.{
        .@"struct" = .{
            .layout = .auto,
            .fields = &fields,
            .decls = &[_]std.builtin.Type.Declaration{},
            .is_tuple = false,
        },
    });
}

const names: []const [:0]const u8 = &.{ "foo", "bar" };
const MyStruct = CreateStruct(names);

pub fn main() !void {
    const my_struct = MyStruct{ .foo = true, .bar = false };
    std.debug.print("{any}\n", .{my_struct});
}
2 Likes

what does alignment mean, especially when considering I want the struct to be packed ? (will need to use .layout = .packed)

I haven’t tested it, but I am think that for a packed struct, you are going to want to keep this value as 0, otherwise it would be the normal alignment that is described in the alignment documentation.

i don’t want to lead you down the wrong path, nor was I able to find any documentation dealing specifically with this scenario, so I would recommend either waiting for someone smarter than I to provide some more information, or try it out and ensure it is working as expected.

2 Likes

packed structs do NOT have an alignment of zero,
it’s the same as if it weren’t packed, so normal.

I think the correct way to interpret the documentation on packed structs is in relation to normal structs.
If it’s not specified, it’s the same as normal structs.
Cant say for sure, though.

The struct itself obviously has an alignment equal to the its backing integer, this is regarding the fields within the packed struct.

Being that there is no padding between fields, the alignment of fields is by definition not going to be the same as if it had a standard layout.

Consider the following:

pub const Temp = packed struct(u32) {
    first: u3,
    second: u16,
    third: u13,
};

What is the alignment of second in this example? if this were a standard struct, the answer would be obvious, but I am not so sure what Zig is expecting the user to supply in this scenario when creating the values themselves. I imagine that internally this is just a 32 bit integer with some masking, and alignment isn’t even a factor here for the fields, though I am very open to being corrected.

1 Like

We can use @typeInfo to look at the fields of Temp:

const std = @import("std");

pub const Temp = packed struct(u32) {
    first: u3,
    second: u16,
    third: u13,
};

pub fn printStructInfo(comptime T: type) void {
    const info = @typeInfo(Temp);
    std.debug.print("{any}", .{T});
    if (info.@"struct".backing_integer) |backing| {
        std.debug.print(" {s}", .{@typeName(backing)});
    }
    std.debug.print("\n", .{});

    inline for (info.@"struct".fields) |f| {
        std.debug.print("{s}:\n", .{f.name});
        std.debug.print("    type:       {s}\n", .{@typeName(f.type)});
        std.debug.print("    alignment:  {d}\n", .{f.alignment});
        std.debug.print("    is_comptime:{}\n", .{f.is_comptime});
        if (f.defaultValue()) |val| {
            std.debug.print("    default val:{any}\n", .{val});
        }
    }
}

pub fn main() !void {
    printStructInfo(Temp);
}

Output:

packedstruct.Temp u32
first:
    type:       u3
    alignment:  0
    is_comptime:false
second:
    type:       u16
    alignment:  0
    is_comptime:false
third:
    type:       u13
    alignment:  0
    is_comptime:false
2 Likes

error: no field named ‘struct’ in union ‘builtin.Type’
lif (info.@“struct”.backing_integer) |backing| {

Regards!

there have been significant changes to builtin between 0.13 and master. It looks like the code is targeting master.

1 Like

Here is a version that runs with zig 0.13.0:

const std = @import("std");

pub const Temp = packed struct(u32) {
    first: u3,
    second: u16,
    third: u13 = 3,
};

pub fn printStructInfo(comptime T: type) void {
    const info = @typeInfo(Temp);
    std.debug.print("{any}", .{T});
    if (info.Struct.backing_integer) |backing| {
        std.debug.print(" {s}", .{@typeName(backing)});
    }
    std.debug.print("\n", .{});

    inline for (info.Struct.fields) |f| {
        std.debug.print("{s}:\n", .{f.name});
        std.debug.print("    type:       {s}\n", .{@typeName(f.type)});
        std.debug.print("    alignment:  {d}\n", .{f.alignment});
        std.debug.print("    is_comptime:{}\n", .{f.is_comptime});
        if (f.default_value) |ptr| {
            const val_ptr: *const f.type = @ptrCast(@alignCast(ptr));
            std.debug.print("    default val:{any}\n", .{val_ptr.*});
        }
    }
}

pub fn main() !void {
    printStructInfo(Temp);
}