-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
idea for std.meta
function to make it a compile error if you forget to use a struct field
#21730
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
Comments
Since it's the standard for fn deinit(foo: *Foo, gpa: std.mem.Allocator) {
gpa.free(foo.b);
gpa.free(foo.c);
foo.* = .{
.a = undefined,
.b = undefined,
.c = undefined,
};
} This will then "notify" you in the |
for something that the compiler should ideally be doing, i really hope this convention eventuallygoes away |
Will also add, the pattern of setting each field to undefined only works if none of the fields have default values, which appears to be the main advantage of this |
I think it was @jamii who wanted exhaustiveness checks? Though, I did stir a storm in a teacup when, upon my suggestion, Rust started emitting more warning for unused fields, so I allow myself to weigh in. As suggested, it honestly gives "the cure is worse than disease" vibes to me. It is cool that it works, but it reads as a too clever a trick, while not actually being all that general? In particular, you'd often want to have dependencies between the fields. For example, if use(foo).* = .{
.gpa = {},
.b = {
foo.gpa.free(foo.b);
},
.c = {
foo.gpa.destroy(foo.c);
},
}; which isn't quite elegant. And it gets worse if a itself needs some handling in drop (eg, reporting memory usage): use(foo).* = .{
.gpa = {},
.b = {
foo.gpa.free(foo.b);
},
.c = {
foo.gpa.destroy(foo.c);
},
};
foo.gpa.reportMemoryUsage(); For ergonomics of these use-cases, I'd maybe prefer unpacking syntax: const gpa, const b, const c = std.meta.asTuple(foo); This of course suffers the field-order dependency, but perhaps we can do const gpa, const b, const c = std.meta.unpackExhaustive(foo, .{.gpa, .b, .c}); ? Can't say I am a fan of either solution, to be honest. It technically gets the job done, but in a maliciously compliant way :-) |
I don't have a strong opinion about this function one way or another. On the one hand it seems a bit hacky (@matklad has covered that well), on the other hand, using it would be optional and it could solve problems. I do have a collections of issues relating to validating Zig source code, which I'm adding this to. Validation appears to me to be an out-of-band domain which might call for a dedicated solution. Validations which can be ensured with comptime magic are a subset of valuable validations, and some of the ones which can be, might be better solved with a different tool. I suspect this is one of those. |
Just a note: the destructuring solution isn't applicable to pinned structures @matklad. |
I definitely would like them, but almost always in places where I'm not actually destroying the original value so switch (expr_data) {
...
.union_init => |union_init| {
const child_dest = switch (dest) {
.value_at => |ptr| wir.Destination{
.value_at = c.box(wir.Walue{ .add = .{
.walue = ptr,
.offset = union_init.repr.tagSizeOf(),
} }),
},
else => dest,
};
const arg = try genExpr(c, f, tir_f, child_dest);
return .{ .@"union" = .{
.repr = union_init.repr,
.tag = union_init.tag,
.value = c.box(arg),
} };
},
...
} If I add a new case to the For the kind of code I often write, exhaustiveness checking catches a lot of bugs and makes changes less scary. Of the features that could realistically be added to zig at this stage, this is the one I miss the most. |
As I read the OP code example, there is no direct link to destruction. |
Djikstra once said that the best formal specification for inverting a matrix is "invert a matrix". The best solution to this class of problem is a way to say "all fields of
pub fn create(allocator: Allocator) !*Stooges {
var stooges = try allocator.create(Stooges);
std.meta.use(stooges).* = .{
.moe = {
stooges.moe = "nyuk nyuk";
},
.larry = {
stooges.moe = "why I oughta!";
},
.curly = {
stooges.curly = "This is gettin' on my noives!";
},
};
return stooges;
} If you add Shemp later, and forget about him (everyone forgets about Shemp), this function has you covered. But Easier to spot? Sure, but we're still looking at code here, when we could be validating it. |
This comment was marked as resolved.
This comment was marked as resolved.
That's a solution to a problem that should not have existed in the first place. If there's ever a danger of forgetting to initialize a field, then don't give it a default init. Most people should not use the default init feature. Zig originally did not have it with no plan to add it. I knew when I added it that people would not understand the nuance of how to use default initialized struct fields, and here we are. There is unfortunately still a use case for default struct field inits which is adding a field while maintaining backwards compatibility in the API, which is why the feature exists. But most people in general should simply never use default struct field inits. |
Oh, right 🤦 |
discussion continues at #21879 |
Thoughts?
cc @matklad this seems relevant to your interests.
The text was updated successfully, but these errors were encountered: