Description
This proposal is extracted from #16512, with more details and justification.
Background
Zig currently has the concept of an "anonymous struct type". This is a type which comes from an anonymous struct literal (.{ ... }
) with no known result type. These types are special: they allow coercions based on structural equivalence which normal structs do not allow. For instance:
const anon = .{ .x = 123 };
const S = struct { x: u32 };
const s: S = anon;
_ = s;
This works because anon
has an anonymous struct type, and all of its field types (in this case comptime x: comptime_int = 123
) are coercible to those of S
, so @TypeOf(anon)
coerces to S
field-wise. Anonymous struct types also allow even stranger coercions, such as allowing these coercions through pointers by creating new constants (e.g. *const @TypeOf(anon)
coerces to *const S
).
Justification
I'm not entirely sure why anonymous struct types exist. My guess is that they originated before RLS, as the method for anonymous initializers to initialize concrete types. In that world, the concept makes sense, but today - with RLS - untyped anonymous initializers are virtually never used. Retaining anonymous struct types significantly complicates the language:
- It introduces a new kind of type which cannot be differentiated by metaprogramming
- It introduces flawed coercions
- It hides potentially expensive copies which could be trivially avoided when coercing to equivalent types
- It leads to less readable code
To pick up on the last point in particular: the only case where anonymous struct types are really used today is when writing code such as the above example. This kind of code would really benefit from a type annotation: it's unclear what anon
is meant to be! Beginners sometimes write this kind of code expecting Zig to use the information from the later lines in type inference (inferring that anon
should have type S
): but anonymous struct types actually mask the issue here, potentially making code "work" whilst being harder to read, slower, and potentially buggier.
Lastly, time to quantify a statement I made a moment ago:
...untyped anonymous initializers are virtually never used.
I looked at the ZIR for a few random files of real Zig code, and noted the following things:
- The total number of struct init instructions
- The number of anonymous struct inits (
struct_init_anon
orstruct_init_anon_ref
) - The number of those anonymous struct inits which would remain if Expand RLS for ref result type #16512 were implemented
Source | Total Struct Inits | Anonymous Struct Inits | Would Remain With Better RLS |
---|---|---|---|
Sema.zig |
1199 | 4 | 0 |
std/array_list.zig |
29 | 1 | 1 (but removing improves code!) |
std/mem.zig |
47 | 1 | 0 |
std/Build.zig |
38 | 0 | 0 |
Bun: js_parser.zig |
814 | 0 | 0 |
Bun: js_ast.zig |
278 | 0 | 0 |
You can see from these numbers that untyped inits rarely happen, and when they do, the proposed RLS improvements would eliminate them. Note that if you try, you can find some files which do genuinely use a lot of anonymous inits right now - for instance arch/x86_64/Lower.zig
in the compiler has 107 at the time of writing - but as far as I can tell from a quick glance every single one of those would be eliminated by #16512. That proposal can essentially be considered a prerequisite of this one.
Proposal
Eliminate anonymous struct types from the language. Untyped struct initializations are still permitted - they are useful for metaprogramming (e.g. std.Build.dependency
's args
parameter) - but they return a "standard" struct
type, with no extra allowed coercions etc.
There's not much else to say. This is a proposal to remove an unnecessary concept from Zig: simplifying the language, encouraging code readability, and making us less prone to issues such as #16862.