How to detect at comptime an struct field that is default initialized to undefined?

I’m doing something like this (and I think it works):

const std = @import("std");
const MyStruct = struct {
    a: i32 = undefined,
    b: i32 = 22,
    c: *i32 = undefined,
    optional: ?*i32 = undefined,
};
pub fn main() !void {
    inline for (@typeInfo(MyStruct).@"struct".fields) |field| {
        if (field.default_value_ptr) |def_val_ptr| {
            const UndefinedComparisonType = @as(*const anyopaque, @as(*const anyopaque, @ptrCast(&@as(field.type, undefined))));

            // @compileLog(field.type);
            // @compileLog(def_val_ptr);
            // @compileLog(UndefinedComparisonType);
            if (comptime def_val_ptr == UndefinedComparisonType) {
                std.debug.print("Found Default initialized field: {s} = undefined\n", .{field.name});
            } else {
                std.debug.print("Found Default initialized field: {s} = {}\n", .{ field.name, field.defaultValue().? });
            }
        } else {
            std.debug.print("Found NOT default initialized field: {s}\n", .{field.name});
        }
    }
}

Which seems to work as it outputs:

Output:
Found Default initialized field: a = undefined
Found Default initialized field: b = 22
Found Default initialized field: c = undefined
Found Default initialized field: optional = undefined

But I’m using a very weird type: UndefinedComparisonType
Is there another way to do it?

Thanks!

No, there is no other way to do it, since undefined cannot be compared at compile time (→causing a compiler error). Also be aware that the way you are doing it, may be patched in the future, so I’d recommend not to rely on it too heavily.

What you can do however, is getting rid of the redundant @as(*const anyopaque. Doing it twice won’t change a thing.

1 Like

I think it is better to not use undefined at all.

const MyStruct = struct {
    a: i32,
    b: i32 = 22,
    c: *i32,
    optional: ?*i32 = null,
};

If defaultValue() is null, it means that a default value is not set.
If defaultValue().? is null, it means that the default value is null.

4 Likes

I think we can help you better if you tell us what you’re trying to do. I tried a few variations on your comptime inline, they do seem to work.

But explicitly setting a default value of undefined is unusual, and I would consider it a bad idea without understanding the reason for doing it.

3 Likes

I have a lot of structs that I want to reflect to an scripting system and I wanted to know which ones will be in an undefined state if I default initialize them in the script system.

I had something like this (But if T has fields defaulted to undefined will not trigger a compiler error) and If I can’t detect it at comptime…

            const default_initialize_fn_generated = struct {
                fn defaultInitialize(ptr: *T) void {
                    ptr.* = T{};
                }
            }.defaultInitialize;

So I will do the opposite. What dimdin proposes, never use undefined for struct fields default initializations. And then in that function check if they have a default value or not.

I know that this function will not compile if a type T, can’t be default initialize by T{}.
But I want to crash only if the function is used at runtime not at compile time so I ended up with this:

const default_initialize_fn_generated = struct {
    fn defaultInitialize(ptr: *T) void {
        var new_T: T = undefined;
        inline for (@typeInfo(T).@"struct".fields) |field| {
            if (comptime field.defaultValue()) |default_field_value| {
                @field(new_T, field.name) = default_field_value;
            } else {
                ds.fatalm("{s} can't be default initialized.", .{@typeName(T)}, @src());
            }
        }
        ptr.* = new_T;
    }
}.defaultInitialize;

Is there a way to know if an struct type can be default initialized without checking all the fields?

Default values are part of the fields of a struct, not the type. afaik the information isnt stored anywhere else

Default-initializing to undefined is a code smell, which suggests the Faulty Default Field Values advice may apply.

7 Likes

It’s possible, using the same stupid trick for getting the name of functions that I had described some time ago:

const std = @import("std");

pub fn main() !void {
    const T = struct {
        number1: i32 = undefined,
        number2: i32 = 123,
        number3: i32 = 456,
        number4: i32 = undefined,
    };
    inline for (@typeInfo(T).@"struct".fields) |field| {
        const ptr: *const field.type = @ptrCast(@alignCast(field.default_value_ptr));
        const name = @typeName(DummyType(ptr.*));
        @compileLog(comptime std.mem.containsAtLeast(u8, name, 1, "undefined"));
    }
}

fn DummyType(comptime value: anytype) type {
    return struct {
        dummy: @TypeOf(value),
    };
}
Compile Log Output:
@as(bool, true)
@as(bool, false)
@as(bool, false)
@as(bool, true)
3 Likes

at least that one wont break in release builds that are more than just a main function.

Definitely cursed though What's the most cursed Zig you can write?

2 Likes

The original doesn’t rely on safe builds or living in main either:

const MyStruct = struct {
    a: i32 = undefined,
    b: u64 = 0xaaaa_aaaa_aaaa_aaaa,
    c: *i32 = undefined,
    d: bool,
    optional: ?*i32 = undefined,
};

test "does this detect undefined or the Screaming Number" {
    inline for (@typeInfo(MyStruct).@"struct".fields) |field| {
        if (field.default_value_ptr) |def_val_ptr| {
            const UndefinedComparisonType = @as(*const anyopaque, @as(*const anyopaque, @ptrCast(&@as(field.type, undefined))));

            // @compileLog(field.type);
            // @compileLog(def_val_ptr);
            // @compileLog(UndefinedComparisonType);
            if (comptime def_val_ptr == UndefinedComparisonType) {
                std.debug.print("Found Default initialized field: {s} = undefined\n", .{field.name});
            } else {
                std.debug.print("Found Default initialized field: {s} = {}\n", .{ field.name, field.defaultValue().? });
            }
        } else {
            std.debug.print("Found NOT default initialized field: {s}\n", .{field.name});
        }
    }
}

This gives the same output in Debug and ReleaseFast builds.

2 Likes

I miss read the code as operating an instance of the struct in which case the values in the fields defaulted to undefined, would be, well undefined, outside of debug and release safe.

1 Like

I did as well at first, which is why I translated it to test form. I still think this is a dodgy idea in most cases, possibly including OP’s, but I remain very impressed with the power of Zig’s comptime introspection of types.