Error when generating struct field names using Zig comptime

I am trying to write a Zig program that uses comptime to generate field names of a struct, but I encounter an error when building the project.

System:
Windows 11
Zig Version:
0.13.0

Below is my code:

const std = @import("std");

var person_field_names = generateFieldNames(Person);

fn generateFieldNames(comptime T: type) []const []const u8 {
    comptime {
        const typeInfo = @typeInfo(T);
        switch (typeInfo) {
            .Struct => |structInfo| {
                const field_count = structInfo.fields.len;
                var field_names: [field_count][]const u8 = undefined;

                var i: usize = 0;
                while (i < field_count) : (i += 1) {
                    const field = structInfo.fields[i];
                    field_names[i] = field.name;
                }
                return &field_names;
            },
            else => @compileError("Only structs are supported!"),
        }
    }
}

const Person = struct {
    name: []const u8,
    age: u8,
};

pub fn main() void {
    for (person_field_names) |name| {
        std.debug.print("Field name: {s}\n", .{name});
    }
}

When I run zig build run, I get the following error:

$ zig build run --summary all
run
└─ run comptime_test
   └─ install
      └─ install comptime_test
         └─ zig build-exe comptime_test Debug native failure
error: the following command exited with error code 3:
C:\softs\zig\zig.exe build-exe -ODebug -Mroot=C:\workspace\zig\comptime_test\src\main.zig --cache-dir C:\workspace\zig\comptime_test\zig-cache --global-cache-dir C:\Users\myhfs\AppData\Local\zig --name comptime_test --listen=-
Build Summary: 0/5 steps succeeded; 1 failed
run transitive failure
└─ run comptime_test transitive failure
   ├─ zig build-exe comptime_test Debug native failure
   └─ install transitive failure
      └─ install comptime_test transitive failure
         └─ zig build-exe comptime_test Debug native (reused)
error: the following build command failed with exit code 1:
C:\workspace\zig\comptime_test\zig-cache\o\9c79a56d0821539e91985b5f4384125d\build.exe C:\softs\zig\zig.exe C:\workspace\zig\comptime_test C:\workspace\zig\comptime_test\zig-cache C:\Users\myhfs\AppData\Local\zig --seed 0x35770c70 -Z8aab571c011cfc33 run --summary all

What is causing this error, and how can I fix it?

1 Like

Welcome to Ziggit @sanomigton!

I think that it crashes is a bug.

I got it to work by assigning the var to a frozen copy and then return that:

const std = @import("std");

var person_field_names = generateFieldNames(Person);

fn generateFieldNames(comptime T: type) []const []const u8 {
    const typeInfo = @typeInfo(T);
    switch (typeInfo) {
        .Struct => |structInfo| {
            const field_count = structInfo.fields.len;
            var field_names: [field_count][]const u8 = undefined;
            for (structInfo.fields, 0..) |field, i| field_names[i] = field.name;
            const frozen = field_names;
            return &frozen;
        },
        else => @compileError("Only structs are supported!"),
    }
}

const Person = struct {
    name: []const u8,
    age: u8,
};

pub fn main() void {
    for (person_field_names) |name| {
        std.debug.print("Field name: {s}\n", .{name});
    }
}

Note there is also std.meta.fieldNames

3 Likes

My problem has been fully resolved, and I can’t thank you enough! Your help truly made a difference. I’m incredibly grateful!

3 Likes

I don’t think it is actually. Comptime mutable values aren’t supposed to escape from their scope, so your fix takes a comptime-variable value, makes it const so that it can be returned, and this is then assigned to a global var, meaning it’s mutable again, but not until runtime.

The rules are a bit confusing and underdocumented, but I think that’s how it’s supposed to work.

Speaking of which @sanomigton, unless you intend to mutate the field names at runtime, you’re better off using const for that variable. Welcome to Ziggit btw. :slight_smile:

Actually maybe? It should cast to const because of the return type. So I think it’s triggering that rule, but in this case it shouldn’t.

But there are other cases where you actually get a compiler error and the code has the analysis of how the values escape their scope, I can’t imagine this is just supposed to crash the compiler. I think this is just a case where that is bugged, maybe it is already fixed in newer versions, otherwise it should become part of an issue and looked at in more detail.

1 Like

No but there’s two kind of “that’s a bug” possible here: the kind where the program shouldn’t compile but the compiler also shouldn’t crash, and the kind where it should compile and the compiler crashes anyway.

I was thinking this was the first kind of program, and upon rethinking it, I’m inclined to say that it’s the second. The coercion to constant in the return type should have taken care of the “mutability cannot escape” part.

It looks like this has been recently fixed in nightly zig. I was able to reproduce the crash with 0.14.0-dev.1730+034577555. But now the compiler catches it. I suspect https://github.com/ziglang/zig/pull/21618 fixed it.

$ zig version
0.14.0-dev.1860+2e2927735
$ zig run /tmp/tmp.zig
/tmp/tmp.zig:3:44: error: global variable contains reference to comptime var
var person_field_names = generateFieldNames(Person);
                         ~~~~~~~~~~~~~~~~~~^~~~~~~~
referenced by:
    main: /tmp/tmp.zig:31:10
    posixCallMainAndExit: ~/zig/zig/download/0.14.0-dev.1860+2e2927735/files/lib/std/start.zig:612:22
    4 reference(s) hidden; use '-freference-trace=6' to see all references
1 Like

Thanks for the clarification! That makes sense—the nuances around comptime mutability and how it transitions to runtime can definitely be tricky. I appreciate you pointing that out.

I’ll update the variable to be const since I’m not planning to mutate the field names at runtime, as you suggested. Also, thanks for the warm welcome to Ziggit! Still getting used to some of the subtleties, but it’s exciting to learn. :blush:

1 Like