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.
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
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.
And a short follow-up:
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.
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.
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.
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.