Error: comptime dereference requires '[:0]const u8' to have a well-defined layout

I am trying to create a function to dynamically generate a new struct with all the input struct fields and an additional field in the output struct type.

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

const Type = builtin.Type;
const print = std.debug.print;

fn generateCommand(comptime input_struct: anytype) type {
    const info = @typeInfo(input_struct);

    if (info != .Struct) {
        @compileError("type must be a struct definition");
    }

    var new_fields = [_]Type.StructField{undefined} ** (info.Struct.fields.len + 1);

    for (info.Struct.fields, 0..) |field, i| {
        new_fields[i] = .{
            .name = field.name,
            .type = field.type,
            .default_value = field.default_value,
            .is_comptime = field.is_comptime,
            .alignment = field.alignment,
        };
    }

    new_fields[info.Struct.fields.len] = .{
        .name = "simple_name",
        .type = [:0]const u8,
        .default_value = "",
        .is_comptime = false,
        .alignment = 1,
    };

    return @Type(.{
        .Struct = .{
            .layout = .auto,
            .backing_integer = null,
            .fields = &new_fields,
            .decls = &[_]builtin.Type.Declaration{},
            .is_tuple = false,
        },
    });
}

const Build = struct {
    path: [:0]const u8,
};

pub fn main() !void {
    const generated = generateCommand(Build){
        .path = "",
    };

    print("{any}\n", .{generated});
}

Here is the output of the above code:

└─ run zig_project_02
   └─ install
      └─ install zig_project_02
         └─ zig build-exe zig_project_02 Debug native 1 errors
src\main.zig:37:12: error: comptime dereference requires '[:0]const u8' to have a well-defined layout  
    return @Type(.{
           ^~~~~
src\main.zig:53:38: note: called from here
    const generated = generateCommand(Build){
                      ~~~~~~~~~~~~~~~^~~~~~~
referenced by:
    callMain: C:\zig\lib\std\start.zig:524:32
    WinStartup: C:\zig\lib\std\start.zig:363:45
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
error: the following command failed with 1 compilation errors:
C:\zig\zig.exe build-exe -ODebug -Mroot=C:\X\Zig\zig_project_02\src\main.zig --cache-dir C:\X\Zig\zig_project_02\.zig-cache --global-cache-dir C:\Users\a\AppData\Local\zig --name zig_project_02 --listen=-
Build Summary: 2/7 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run zig_project_02 transitive failure
   ├─ zig build-exe zig_project_02 Debug native 1 errors
   └─ install transitive failure
      └─ install zig_project_02 transitive failure
         └─ zig build-exe zig_project_02 Debug native (reused)
error: the following build command failed with exit code 1:
C:\X\Zig\zig_project_02\.zig-cache\o\5dd6fef7a9d5dd514a3162aaaecfed38\build.exe C:\zig\zig.exe C:\X\Zig\zig_project_02 C:\X\Zig\zig_project_02\.zig-cache C:\Users\a\AppData\Local\zig --seed 0x27a5a29b -Za3622ac6ec9178fd run

Hello @legend
Welcome to ziggit :slight_smile:

The syntax to construct a Build object is: Build{ .path = ""}.

Move the closing parenthesis after the Build construction:

pub fn main() !void {
    const generated = generateCommand(Build{
        .path = "",
    });

    print("{any}\n", .{generated});
}

For this you can just use .decls = &.{},.

.default_value is of type *const anyopaque it is a type erased pointer to a value of type .type, if you follow the types you end up with this:

.default_value = @ptrCast(@as(*const [:0]const u8, &"")),

If we use @compileLog(&"");
we get:

Compile Log Output:
@as(*const *const [0:0]u8, @as(*const [0:0]u8, &.{}))

&"" because we need a pointer to the value not the value itself, the @as to convert the *const *const [0:0]u8 to the slice type, the @ptrCast to convert it to *const anyopaque.


This can be written like this:

var new_fields: [info.Struct.fields.len + 1]Type.StructField = undefined;

Also it is Zig convention to write functions that return a type, starting with a capital letter, so generateCommand should be GenerateCommand.


Because your generateCommand function actually expects a struct type it is better to use comptime input_struct: type this means that when somebody calls generateCommand with values that aren’t types, like 42 or "hello", you get a better error message and the parameter type already rejects everything that isn’t a type.

Instead of getting a stack trace that points inside your function:

generatestruct.zig:8:18: error: expected type 'type', found 'comptime_int'
    const info = @typeInfo(input_struct);
                 ^~~~~~~~~~~~~~~~~~~~~~~
generatestruct.zig:51:30: note: called from here
    const G = GenerateCommand(42);
              ~~~~~~~~~~~~~~~^~~~

You get this:

generatestruct.zig:51:31: error: expected type 'type', found 'comptime_int'
    const G = GenerateCommand(42);
                              ^~
generatestruct.zig:7:43: note: parameter type declared here
fn GenerateCommand(comptime input_struct: type) type {
                                          ^~~~
3 Likes

That worked like a charm. A follow up question, how to add a method to the reified struct. because std.builtin.Type.Struct has no field or member to add a methods or a functions.

the formatter auto-formats it by bringing it to next line as I have a habit of adding a trailing commas at the end… :sweat_smile:

You can’t. :man_shrugging:

You can’t add methods or declarations to types created with @Type if you want to do that you could wrap it in a generic struct instead. Something like:

pub fn Wrapper(comptime T:type) type {
    return struct {
       const Self = @This();
       wrapped: T,

       pub fn increment(self:*Self) void {
           self.wrapped.counter += 1;
       }
       ...
    }
}

If you don’t like the wrapper you also can do it the other way around, embed a generic struct with extra methods in a field of the otherwise unmodified struct:

const BuildOther = struct {
    path: [:0]const u8,
    command: Command(@This(), "command") = .{},
};

pub fn Command(comptime T: type, comptime field: []const u8) type {
    return struct {
        const Self = @This();

        pub fn print(self: *const Self) void {
            // get the struct where the command is embedded within
            const parent: *const T = @alignCast(@fieldParentPtr(field, self));
            std.debug.print("command print: {}\n", .{parent});
        }
    };
}

pub fn main() !void {
    const b: BuildOther = .{ .path = "" };
    b.command.print();
}

This has the benefit of avoiding reconstruction of the original struct, thus you can easily add methods to BuildOther by just writing them directly in the struct definition, you also can add methods to it like this:

const BuildOther = struct {
    path: [:0]const u8,
    command: Command(@This(), "command") = .{},

    pub const doSomething = DoSomething(SomeType).impl;
};

For that approach look at this topic:

The command field I have used is basically a field-based mixin, the benefit of those is that they can be added via a field that also functions as its own namespace separating the functionality of this mixin from clashing with other possible mixins.

One of the issues with usingnamespace based mixins is that it could cause clashes when used with multiple mixins.

Personally I think it is best to avoid constructing structs based on a “template” struct and instead just write the struct you want directly and possibly adding some methods to it using some of these techniques.

Finally if you really, really want to generate the struct you could use a buildstep to generate the code and then create a module from it and thus you also would be able to generate methods, but I would explore other options first.