-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
builtin function @reify to create a type from a TypeInfo instance #383
Comments
For functions, would it make sense to also expose the number of arguments and make them individually accessible? (cc @AndreaOrru ) |
Yes, that would be super useful to implement the IPC in my kernel, for example. |
Added a third item for this above |
cc @hasenj
|
Is the type even needed? I think a list of names as strings should suffice, given that there are other builtin functions that can be used to reflect on the type. For example:
|
It seems like with just these features we could implement a structural based interface mechanism, similar to go. Here is a motivating example to implement a print trait of sorts, allowing any struct which implements a
|
With this example, if you were going to do |
You're right. This example would really only provide slightly more targeted error messages. A better example would be a |
Since zig can store types as regular data, I was wondering if it would be better to, rather than have magic fields that make
etc. You get the idea. You could also add a souped-up version of Some pros: possibly easier documentation (you can now lookup what fields you can access just like for regular structs), fewer special cases in the field-lookup code. There could also be a Of course, |
I like this idea. I ran into an issue which is related which I will type up and link here. |
We could potentially even remove these functions:
Instead these could all be fields or member functions of the struct returned from |
I like the idea of The With the "maketype" functionality we could write type safe compile time type generators that can give proper compiler errors and flexibly generate types. I think we need to be able to name the types too if we want to export them to C as a visible structs. On Zig side we can just use aliases to address the generated types if we want to. |
see #383 there is a plan to unify most of the reflection into 2 builtin functions, as outlined in the above issue, but this gives us needed features for now, and we can iterate on the design in future commits
I had this same idea while looking over some older reflection code I'd written and threw together an example of what I imagine a TypeInfo struct (returned by @reify) might look like. This is with no knowledge of compiler internals, it's just spitballing. This example uses pointer reform syntax as described by #770
|
I think the solution to all of these type of things would be to expose the LLVM bindings, and allow them to be used at comptime on Zig code. |
I don't really like having syntax to emit fields from an var outType = struct {
// ... everything that doesn't need to be generated dynamically ...
};
const inFields = &@typeInfo(inputType).Struct.fields;
const originalFields = &@typeInfo(outType).Struct.fields;
var outFields: [originalFields.len + inFields.len]StructField = undefined;
for (originalFields) |field, i| {
outFields[i] = field;
}
for (inFields) |field, i| {
outFields[i + originalFields.len] = do_something_with(field);
}
@typeInfo(mytype).Struct.fields = &outFields; |
I'm not sure types should be mutable, in your example (I assume the Generating fields, definitions and such inline has the advantage that they happen when the type is created and you can't end up with a mismatch that would necessitate more language rules. Also, I don't really see what it adds that wouldn't be otherwise possible; yet it's introducing a new way of defining types on top of the already existing one. (same goes for |
Not knowing about this proposal, I just created a duplicate proposal for the Only difference being that I called it I created it because I found a use case for it which I describe here if you want to take a look: #2782 (comment) |
I think it's a good use case, and I think it asks the question, "Even if we don't go full bananas on I'm going to make a separate issue for that. |
This issue is now blocking on #2907. |
Another random thought on what |
Related to AoS to SoA transforms is creating an archetype based ECS. For example, given a user defined archetype with some component fields, I'd like to be able to generate an appropriate concrete "storage" type (here storage is shown by simple slices). const MyArchetype = struct {
location: Vec3,
velocity: Vec3,
};
// I then write some function Storage() that uses the @reify feature
// const MyArchetypeStorage = Storage(MyArchetype);
// I want to generate something like this:
const MyArchetypeStorage = struct {
location: []Vec3,
velocity: []Vec3,
} Modern game engines tend to jump through lots of hoops (and runtime costs) to pay for this abstraction, because it enables things like batched compile-time dispatch (if I have a bunch of storages I can run some function on each one that contains a particular set of batched field). Would be great to be able to generate things like this. |
I recently came across a problem which There's no neat way of currently solving this in zig without resorting to code generation, or writing massive amounts of code. With This would greatly simplify and reduce the footprint of the code, which would be great for my sanity and for spotting bugs. |
I would recommend having a look at Circle (https://www.circle-lang.org/). It's a compile-time machinery on top of C++. It's sometimes complex (not saying it in a bad way - C++ itself is very complex) but it does pass the printf test. (It's possible to create printf without compiler hooks.) And it does much more than that. |
I'm running into a desire for full-strength |
After some discussion with @alexnask in IRC we came to the conclusion that allowing People are already working around the restrictions that you cannot create The question that was not clear is whether to allow creating |
For reference, the following is achievable today: const std = @import("std");
// Create a tuple with one runtime-typed field
fn UniTuple(comptime T: type) type {
const Hack = struct {
var forced_runtime: T = undefined;
};
return @TypeOf(.{Hack.forced_runtime});
}
/// Types should be an iterable of types
fn Tuple(comptime types: anytype) type {
const H = struct {
value: anytype,
};
var empty_tuple = .{};
var container = H{
.value = empty_tuple,
};
for (types) |T| {
container.value = container.value ++ UniTuple(T){ .@"0" = undefined };
}
return @TypeOf(container.value);
}
pub fn StructType2(comptime names: []const []const u8, comptime types: anytype) type {
std.debug.assert(names.len == types.len);
const Storage = Tuple(types);
return struct {
const field_names = names;
const field_types = types;
const Self = @This();
storage: Storage,
pub fn create(literal: anytype) Self {
comptime std.debug.assert(std.meta.fields(@TypeOf(literal)).len == field_names.len);
comptime std.debug.assert(std.meta.trait.hasFields(@TypeOf(literal), field_names));
var self: Self = undefined;
inline for (field_names) |name, idx| {
self.storage[idx] = @field(literal, name);
}
return self;
}
fn fieldIndex(comptime name: []const u8) ?comptime_int {
var i = 0;
while (i < field_names.len) : (i += 1) {
if (std.mem.eql(u8, name, field_names[i])) return i;
}
return null;
}
fn FieldType(comptime name: []const u8) type {
const idx = fieldIndex(name) orelse @compileError("Field '" ++ name ++ "' not in struct '" ++ @typeName(Self) ++ "'.");
return field_types[idx];
}
pub fn field(self: anytype, comptime name: []const u8) if (@TypeOf(self) == *Self) *FieldType(name) else *const FieldType(name) {
const idx = fieldIndex(name).?;
return &self.storage[idx];
}
pub fn format(
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
try writer.writeAll(@typeName(Self));
if (std.fmt.default_max_depth == 0) {
return writer.writeAll("{ ... }");
}
try writer.writeAll("{");
inline for (field_names) |f, i| {
if (i == 0) {
try writer.writeAll(" .");
} else {
try writer.writeAll(", .");
}
try writer.writeAll(f);
try writer.writeAll(" = ");
try std.fmt.formatType(self.storage[i], fmt, options, writer, std.fmt.default_max_depth - 1);
}
try writer.writeAll(" }");
}
};
}
pub fn StructType(comptime fields: anytype) type {
var field_names: [fields.len][]const u8 = undefined;
var field_types: [fields.len]type = undefined;
for (fields) |f, idx| {
field_names[idx] = f[0];
field_types[idx] = f[1];
}
return StructType2(&field_names, &field_types);
}
pub fn main() void {
const FooStruct = StructType(.{
.{ "a", usize },
.{ "b", bool },
});
var foo = FooStruct.create(.{ .a = 0, .b = false });
foo.field("a").* = 1;
std.debug.print("{}\n", .{foo});
} This is a simple implementation of a |
I think this is a strong argument. I'd like to "accept" |
Note on decls: perhaps we could sidestep the issue by requiring method definitions, and in fact all decl values, be written by value? That is, (in the case of methods) use a function which has already been defined, rather than constructing it from scratch. #1717 will make this more symmetric for methods vs variables/constants. (No examples as I'm on mobile -- hopefully I'm being clear enough.) |
This is now implemented for every type, and can generate everything that syntax can except
I think this issue should be renamed to be about allowing declarations, or closed and replaced with a new one for that. |
sounds good 👍 |
T.child_type
field.T.return_type
fieldT.is_var_args
andT.arg_types
which is of type[N]type
@fieldsOf(T)
whereT
is a struct or enum. Returns anonymousstruct { T: type, name: []const u8 }
@typeInfo
@reify
cc @raulgrell
The text was updated successfully, but these errors were encountered: