Background
In Zig, the struct keyword can be used to create two distinct categories of type:
- "Struct types", which have named fields, are container types, which means:
- They have an associated namespace, so can contain declarations.
- Their fields can have doc comments.
- They participate in lazy type resolution, allowing self-reference through pointers. e.g.
const IntList = struct { next: ?*IntList, data: u32 };is accepted.
- "Tuple types", which have unnamed fields, are structural types, which means:
- They do not have an associated namespace, and cannot contain declarations.
- Their fields cannot have doc comments.
- They do not participate in lazy type resolution, so self-reference through pointers is disallowed. e.g.
const IntList = struct { ?*IntList, u32 };triggers a compile error.
Tuple types are more constrained than struct types in other ways, too:
- They do not support
externorpacked(soon to be calledbitpack) layouts, instead always having ill-defined/"auto" layout.- Because tuples cannot have
packed(bitpack) layout, they also cannot have backing integer types.
- Because tuples cannot have
- Their fields cannot have default values, except for
comptimefields. - Their fields cannot have
alignannotations.
Put together, there are very few similarities between struct types and tuple types, so using the term "struct" for both of them can be very confusing to beginners! (In fact, tuple types have a lot more in common with array types, which are also structural, than they do with struct types.)
https://github.com/ziglang/zig/pull/23733 began to draw a line between struct types and tuple types by introducing separate builtins for reifying those types (@Struct for reifying struct types, and @Tuple for reifying tuple types). Put simply, this proposal suggests that we finish the job.
Proposal
Stop using the term "struct" to refer to tuple types in any context. To the best of my knowledge, this will require two language changes.
Firstly, std.builtin.Type (the tagged union returned by @typeInfo) will gain a new field, named tuple. This diff (approximately) will be applied to lib/std/builtin.zig:
diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig
index 97846ed035..0ce58defcb 100644
--- a/lib/std/builtin.zig
+++ b/lib/std/builtin.zig
@@ -560,6 +560,7 @@ pub const Type = union(enum) {
float: Float,
pointer: Pointer,
array: Array,
+ tuple: Tuple,
@"struct": Struct,
comptime_float,
comptime_int,
@@ -655,6 +656,12 @@ pub const Type = union(enum) {
}
};
+ /// This data structure is used by the Zig language code generation and
+ /// therefore must be kept in sync with the compiler implementation.
+ pub const Tuple = struct {
+ field_types: []const type,
+ };
+
/// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const ContainerLayout = enum(u2) {
@@ -702,7 +709,6 @@ pub const Type = union(enum) {
backing_integer: ?type = null,
fields: []const StructField,
decls: []const Declaration,
- is_tuple: bool,
};
/// This data structure is used by the Zig language code generation and
I have omitted comptime field values above, because this proposal interacts with #32045 and #32046, but such information would be included too if still applicable.
Secondly, the "tuple declaration syntax", which uses the struct keyword, will be removed. The syntax in question is (e.g.) struct { u32, u64 }. For the avoidance of doubt, struct {} (with no fields) remains valid: even today that declares a struct type rather than a tuple type.
If this syntax is removed, an obvious question is whether it should have a direct replacement. For instance, we could theoretically reserve tuple as a keyword and introduce the syntax tuple { u32, u64 }. However, in my opinion, this change would be undesirable: reserving new keywords generally requires heavy justification, yet as @andrewrk so eloquently put it, Tuples Are Generally Bad.
We want to avoid people overusing tuples, because structural types can have awkward behavior (e.g. type dependency loops) and tuple overuse can result in less readable code due to fields being unnamed (and likely undocumented). Including a special syntax for declaring tuple types therefore elevates them to a higher status than we necessarily want.
Thanks to https://github.com/ziglang/zig/pull/23733, we actually already have a relatively clean-looking way to specify a tuple type which does not require dedicated syntax: @Tuple(&.{ u32, u64 }). I believe this builtin represents the optimal solution to this problem: tuple types are straightforward to declare where reasonable, and not overly noisy, but we avoid providing special syntax to create them, which slightly simplfies the language grammar and increases perceived friction enough to hopefully mitigate overuse of tuples.
@Tuple cannot currently reify tuple types with comptime fields. If this proposal is accepted, and comptime fields remain in the language, this must be changed when removing the struct { A, B } syntax; however, note the existence of #32045.
Oh, and because tuple types are fairly restrictive, it would probably be fine to have zig fmt migrate struct { A, B, C } to @Tuple(&.{ A, B, C }) if we want.