Things Zig comptime Won't Do

26 Likes

Beauty of a well design language – you can cover a lot of ground with a small number of strategically important features.

In Zig, my favorite mechanism is the comptime duck-typing. Python became popular partly because of its runtime duck-typing. I hope Zig will catch the wind with zero-runtime-cost duck-typing at compile time.

2 Likes

However, it is impossible to add methods to generated types, they must be inert bundles of fields.

I’ve never tried doing this with methods, but it seems like I can gather arbitrary sets of fields on a type. I recently did this in my new windowing project zin:

pub fn Callback(window_config: WindowConfig) type {
    return makeTaggedUnion(
        &([_]Field{
            .{ .name = "close", .type = void },
            .{ .name = "draw", .type = Draw(window_config) },
        } ++ (if (window_config.data().key_events) [_]Field{
            .{ .name = "key", .type = Key },
        } else [_]Field{}) ++ (if (window_config.data().mouse_events) [_]Field{
            .{ .name = "mouse", .type = Mouse },
        } else [_]Field{}) ++ (if (window_config.data().timers) [_]Field{
            .{ .name = "timer", .type = usize },
        } else [_]Field{})),
    );
}

This code generates a tagged union that looks like this:

const Callback = union(enum) {
    // only include this if app wants mouse events
    mouse: Mouse,
    // only include this if app wants key events
    key: Key,
    // etc...
};

Couldn’t I use a similar technique to create a type and filter on decls?

P.S. here’s the makeTaggedUnion function:

const Field = struct {
    name: [:0]const u8,
    type: type,
};
fn makeTaggedUnion(fields: []const Field) type {
    const EnumField = std.builtin.Type.EnumField;
    const UnionField = std.builtin.Type.UnionField;

    var enum_fields: [fields.len]EnumField = undefined;
    var union_fields: [fields.len]UnionField = undefined;

    for (fields, 0..) |field, i| {
        enum_fields[i] = .{ .name = field.name, .value = i };
        union_fields[i] = .{ .name = field.name, .type = field.type, .alignment = @alignOf(field.type) };
    }

    return @Type(std.builtin.Type{
        .@"union" = .{
            .layout = .auto,
            .tag_type = @Type(std.builtin.Type{ .@"enum" = .{
                .tag_type = std.math.IntFittingRange(0, fields.len - 1),
                .fields = &enum_fields,
                .decls = &.{},
                .is_exhaustive = true,
            } }),
            .fields = &union_fields,
            .decls = &.{},
        },
    });
}

Well, for now one can ā€œinjectā€ declarations as mixin with the help of usingnamespace. But if and when usingnamespace is deprecated there will be no alternative sound way of implementing mixins.

No, you can’t create a Decl to stuff in .decls

there’s a horrible hack via comptime fields though, which I am not showing here

2 Likes

Interesting, I feel like I may have read this years ago but do you have any insight as to why there’s an extra restriction on decls?

here is Andrew on why he rejected a proposal to add it.

there are a few discussions on this site discussing it and alternatives/ways to try to accomplish it.

2 Likes

And a short follow-up:

8 Likes

and unfortunately you also can’t do this the other way around either - a type reified via @Type() can have arbitrary fields but no decls, and a type created as a comptime struct can have declarations but not arbitrary fields.

I think the best solution is usually to wrap the latter into the former (have an outer type with methods etc that contains a single ā€œfieldsā€ field of a reified struct type containing the actual fields), but this feels like an ugly arbitrary restriction. For example I don’t think you can make an elegant ā€œPartialā€ type (with all fields optional) with conversion methods to/from the original type.

2 Likes

This would’ve been nice for wayland. In zeppelin, I have to use very ugly syntax now: xdg_toplevel.request(.set_fullscreen, .{ .output = null }); and xdg_toplevel.requestNewObject(...) if it returns something, instead of xdg_toplevel.set_fullscreen(null)

Zig comptime could have been a nice alternative to code generation.

This is the way. It takes a little getting used to, but doesn’t actually feel cumbersome (to me at least). I kind of like the visual separation of generated fields from ā€˜regular’ fields.

1 Like

It still can be, and this is the best case for finishing the implementation of type reification rather than leaving it in its current state.

You can have a world where Zig code is never run through m4, or a world where comptime manipulation of namespaces and behaviors is not possible. But you can’t have both.

2 Likes

I’m pretty sure type reification is finalized. Andrew made it clear he doesn’t want declarations inside reified types.

Declaring that something unfinished will remain unfinished is not the same thing as finishing it.

1 Like