ziglang/zig
259
6.0k
Fork
You've already forked zig
771

Remove comptime fields from the language #32045

Open
opened 2026年04月23日 15:02:07 +02:00 by mlugg · 15 comments
Owner
Copy link

Background

In Zig, fields on struct and tuple types can be marked as comptime. Such fields must specify a default field value. When comptime fields are loaded, they always contain the comptime-known field value specified at the type declaration site. These fields do not appear in the runtime memory layout of the type, lack meaningful addresses, and cannot vary between different values of the same type. In many ways, they are more akin to declarations than to fields.

Rationale

There are two main motivations for comptime fields existing in Zig:

  • When using @call, it may be necessary to specify comptime-known values for comptime parameters, but runtime-known values for other parameters. comptime tuple fields allow for this: the tuple fields corresponding to comptime parameters must be comptime fields, but the other fields can be normal fields, and a runtime-known value of this tuple type can be passed to @call.

  • It allows functions which accept their own argument tuples, like std.debug.print (and other formatted printing functions), to accept a mix of comptime-only values and runtime-known values, e.g: std.debug.print("runtime value {d}, comptime-only {any}\n", .{ runtime_int, u32 });

However, in exchange for these benefits, comptime fields act as a major point of complication and confusion in the language. Almost as a rule, when a beginner to Zig discovers the existence of comptime fields, they assume that they can be mutated at compile time, because the actual behavior of comptime fields is very unintuitive. (Of course, allowing these fields to be mutated at comptime has been proposed in the past; but unfortunately such a change would introduce an unacceptable amount of language complexity.) In terms of the langauge specification, because comptime fields are not associated with any particular instance of a struct/tuple in memory, they act as awkward exceptions to the usual type layout rules (for instance aligning these fields does not affect the natural alignment of the struct type containing them), and pointers to these fields act unusually (almost like pointers to const declarations, but not quite), such as in their lack of support for @fieldParentPtr (because the field pointer is not associated to any specific struct instance).

This doesn't really matter to the proposal, but it is worth quickly noting that the justifications given above for the existence of comptime fields only really apply to tuple fields: @call only accepts tuples, and while our formatted printing functions can accept struct types, that functionality is used somewhat rarely.

Proposal

Remove comptime fields from Zig. This renders a very small handful of operations impossible, in exchange for a simplified grammar, a significantly simplified compiler implementation and language specification, and the elimination of a frequent point of confusion for beginners.

Note that because only comptime fields can have default values in tuple types (this is already the case), this proposal completely eliminates default values from tuple types.

Below is some brief discussion of the two affected use cases (the ones I listed above as motivations for the existence of comptime fields).


This proposal makes it impossible to directly use @call at runtime on functions with comptime parameters:

fnaddComptimeRuntime(comptimea:u32,b:u32)u32{returna+b;}fnadd10(x:u32)u32{constlhs=10;// Assume that `@call` is necessary here. For instance, perhaps a specific modifier is needed.// This call works today, but would be a compile error under this proposal, because the first// argument must be comptime-known while the second argument is runtime-known.return@call(.auto,addComptimeRuntime,.{lhs,x});}pubfnmain()void{_=add10(100);}

However, it is still possible to create a wrapper function which captures your comptime parameter values in closure, so add10 could be rewritten like this:

fnadd10(x:u32)u32{constlhs=10;conststatic=struct{fnadd10Inner(x:u32)u32{returnaddComptimeRuntime(lhs,x);}};return@call(.auto,static.add10Inner,.{x});}

To be clear, I am in no way claiming that this is better than status quo: I am merely pointing out that the use case can still be satisfied, albeit in a more awkward way. I don't think this is a real problem for what is a quite rare use case.

It is also worth noting that the main use case for @call in practice is functions like std.Io.async which accept, as arguments, a function and a tuple of arguments to pass to it with @call. This use case already tends to fail for comptime parameters today due to the (very reasonble!) use of std.meta.ArgsTuple, which gives a tuple type containing no comptime fields. That is, the following snippet will emit a compile error:

fnaddComptimeRuntime(comptimea:u32,b:u32)u32{returna+b;}pubfnmain(init:std.process.Init)void{constlhs=10;constrhs=10;varfuture=init.io.async(addComptimeRuntime,.{lhs,rhs});_=future.await(init.io);}conststd=@import("std");

(...okay, well, apparently right now it actually crashes the compiler! Sorry about that---although I think the fact that not many people have hit this bug reinforces the argument that this is not a common use case.)

So to make that work, you already need to use comptime closure (as discussed above), like this:

fnaddComptimeRuntime(comptimea:u32,b:u32)u32{returna+b;}pubfnmain(init:std.process.Init)void{constlhs=10;constrhs=20;conststatic=struct{fninner()u32{returnaddComptimeRuntime(lhs,rhs);}};varfuture=init.io.async(static.inner,.{});_=future.await(init.io);}conststd=@import("std");

So, since this use case is very rare, can still be served (albeit in a more awkward manner), and the pattern being broken already frequently fails today, I do not think this is a serious concern.


This proposal makes it impossible to call formatted printing functions like std.debug.print on mixed comptime-only values and runtime-known values, such as the following:

pubfnmain()void{foo(123);}fnfoo(x:u32)void{std.debug.print("value {d} of type {any}\n",.{x,@TypeOf(x)});}conststd=@import("std");

(This is also hit if you use an uncoerced numeric literal as an argument to formatted printing, because comptime_int and comptime_float are comptime-only types.)

I don't have much to say about this one: I simply do not believe this use case is common or valuable enough to be worth the language complexity it invokes. The only case where I think someone would realistically be hindered by this is initial experiementation while learning Zig (for instance, writing a numeric literal or a type as a print argument when first learning the formatted printing API). Don't get me wrong, that is a valuable use case!--but I don't think it can justify the complexity of this language feature.

## Background In Zig, fields on struct and tuple types can be marked as `comptime`. Such fields *must* specify a default field value. When `comptime` fields are loaded, they always contain the comptime-known field value specified at the type declaration site. These fields do *not* appear in the runtime memory layout of the type, lack meaningful addresses, and cannot vary between different values of the same type. In many ways, they are more akin to declarations than to fields. ## Rationale There are two main motivations for `comptime` fields existing in Zig: * When using `@call`, it may be necessary to specify comptime-known values for `comptime` parameters, but runtime-known values for other parameters. `comptime` tuple fields allow for this: the tuple fields corresponding to `comptime` parameters must be `comptime` fields, but the other fields can be normal fields, and a runtime-known value of this tuple type can be passed to `@call`. * It allows functions which accept their own argument tuples, like `std.debug.print` (and other formatted printing functions), to accept a mix of comptime-only values and runtime-known values, e.g: `std.debug.print("runtime value {d}, comptime-only {any}\n", .{ runtime_int, u32 });` However, in exchange for these benefits, `comptime` fields act as a major point of complication and confusion in the language. Almost as a rule, when a beginner to Zig discovers the existence of `comptime` fields, they assume that they can be *mutated* at compile time, because the actual behavior of `comptime` fields is very unintuitive. (Of course, allowing these fields to be mutated at comptime has been proposed in the past; but unfortunately such a change would introduce an unacceptable amount of language complexity.) In terms of the langauge specification, because `comptime` fields are not associated with any particular instance of a struct/tuple in memory, they act as awkward exceptions to the usual type layout rules (for instance aligning these fields does not affect the natural alignment of the `struct` type containing them), and pointers to these fields act unusually (*almost* like pointers to `const` declarations, but not quite), such as in their lack of support for `@fieldParentPtr` (because the field pointer is not associated to any specific struct instance). This doesn't really matter to the proposal, but it is worth quickly noting that the justifications given above for the existence of `comptime` fields only really apply to *tuple* fields: `@call` only accepts tuples, and while our formatted printing functions *can* accept struct types, that functionality is used somewhat rarely. ## Proposal Remove `comptime` fields from Zig. This renders a very small handful of operations impossible, in exchange for a simplified grammar, a significantly simplified compiler implementation and language specification, and the elimination of a frequent point of confusion for beginners. Note that because only `comptime` fields can have default values in tuple types (this is already the case), this proposal completely eliminates default values from tuple types. Below is some brief discussion of the two affected use cases (the ones I listed above as motivations for the existence of `comptime` fields). --- This proposal makes it impossible to directly use `@call` at runtime on functions with `comptime` parameters: ```zig fn addComptimeRuntime(comptime a: u32, b: u32) u32 { return a + b; } fn add10(x: u32) u32 { const lhs = 10; // Assume that `@call` is necessary here. For instance, perhaps a specific modifier is needed. // This call works today, but would be a compile error under this proposal, because the first // argument must be comptime-known while the second argument is runtime-known. return @call(.auto, addComptimeRuntime, .{ lhs, x }); } pub fn main() void { _ = add10(100); } ``` However, it is still possible to create a wrapper function which captures your `comptime` parameter values in closure, so `add10` could be rewritten like this: ```zig fn add10(x: u32) u32 { const lhs = 10; const static = struct { fn add10Inner(x: u32) u32 { return addComptimeRuntime(lhs, x); } }; return @call(.auto, static.add10Inner, .{x}); } ``` To be clear, I am in no way claiming that this is better than status quo: I am merely pointing out that the use case can still be satisfied, albeit in a more awkward way. I don't think this is a real problem for what is a quite rare use case. It is also worth noting that the main use case for `@call` in practice is functions like `std.Io.async` which accept, as arguments, a function and a tuple of arguments to pass to it with `@call`. This use case already tends to fail for `comptime` parameters *today* due to the (very reasonble!) use of `std.meta.ArgsTuple`, which gives a tuple type containing no `comptime` fields. That is, the following snippet will emit a compile error: ```zig fn addComptimeRuntime(comptime a: u32, b: u32) u32 { return a + b; } pub fn main(init: std.process.Init) void { const lhs = 10; const rhs = 10; var future = init.io.async(addComptimeRuntime, .{ lhs, rhs }); _ = future.await(init.io); } const std = @import("std"); ``` (...okay, well, apparently right now it actually crashes the compiler! Sorry about that---although I think the fact that not many people have hit this bug reinforces the argument that this is not a common use case.) So to make that work, you *already* need to use comptime closure (as discussed above), like this: ```zig fn addComptimeRuntime(comptime a: u32, b: u32) u32 { return a + b; } pub fn main(init: std.process.Init) void { const lhs = 10; const rhs = 20; const static = struct { fn inner() u32 { return addComptimeRuntime(lhs, rhs); } }; var future = init.io.async(static.inner, .{}); _ = future.await(init.io); } const std = @import("std"); ``` So, since this use case is very rare, *can* still be served (albeit in a more awkward manner), and the pattern being broken already frequently fails today, I do not think this is a serious concern. --- This proposal makes it impossible to call formatted printing functions like `std.debug.print` on mixed comptime-only values and runtime-known values, such as the following: ```zig pub fn main() void { foo(123); } fn foo(x: u32) void { std.debug.print("value {d} of type {any}\n", .{ x, @TypeOf(x) }); } const std = @import("std"); ``` (This is also hit if you use an uncoerced numeric literal as an argument to formatted printing, because `comptime_int` and `comptime_float` are comptime-only types.) I don't have much to say about this one: I simply do not believe this use case is common or valuable enough to be worth the language complexity it invokes. The only case where I think someone would realistically be hindered by this is initial experiementation while learning Zig (for instance, writing a numeric literal or a type as a `print` argument when first learning the formatted printing API). Don't get me wrong, that is a valuable use case!--but I don't think it can justify the complexity of this language feature.
mlugg added this to the Urgent milestone 2026年04月23日 15:02:07 +02:00
Contributor
Copy link

A current idiom (hack?) is to use the is_comptime field to determine if a value is comptime-known, which can be used for certain optimizations similar to @inComptime:

conststd=@import("std");pubinlinefnisComptimeKnown(x:anytype)bool{return@typeInfo(@TypeOf(.{x})).@"struct".fields[0].is_comptime;}testisComptimeKnown{constx:i32=1;trystd.testing.expect(isComptimeKnown(x));vary:i32=undefined;y=2;trystd.testing.expect(!isComptimeKnown(y));}

If comptime fields are removed, would you consider pursuing an @isComptimeKnown builtin (new intrinsic: @isComptime(value: var) bool #2128)?

A current idiom (hack?) is to use the `is_comptime` field to determine if a value is comptime-known, which can be used for certain optimizations similar to `@inComptime`: ```zig const std = @import("std"); pub inline fn isComptimeKnown(x: anytype) bool { return @typeInfo(@TypeOf(.{x})).@"struct".fields[0].is_comptime; } test isComptimeKnown { const x: i32 = 1; try std.testing.expect(isComptimeKnown(x)); var y: i32 = undefined; y = 2; try std.testing.expect(!isComptimeKnown(y)); } ``` If `comptime` fields are removed, would you consider pursuing an `@isComptimeKnown` builtin ([new intrinsic: @isComptime(value: var) bool #2128](https://github.com/ziglang/zig/issues/2128))?

I personally have used both @call and std.fmt with comptime (fields in the tuple) arguments before, and would be slightly annoyed by their direct removal without replacement.

That said, I've already come up with a generic userland wrapper pattern of splitting runtime from `comptime` arguments, which I think would sufficiently supplement the current API:

.
The basic idea is to split all arguments into two aggregates (either struct or tuple), and add a helper function eitherField("a", c_args, r_args) to smooth this split over to make it even less obvious in callee code:

// helper functioninlinefnfieldOfWhich(comptimename:[]constu8,comptimeC:type,R:type)enum{c,r}{returncomptimewhich:{if(@hasField(C,name)){if(@hasField(R,name))@compileError("field '"++name++"' contained in both argument types '"++@typeName(@TypeOf(C))++"' and '"++@typeName(@TypeOf(R))++"' (ambiguous)");break:which.c;}if(!@hasField(R,name))@compileError("field '"++name++"' contained in neither argument type '"++@typeName(@TypeOf(C))++"' nor '"++@typeName(@TypeOf(R))++"'");break:which.r;};}fnEitherField(comptimename:[]constu8,comptimec:type,comptimer:type)type{returnswitch(comptimefieldOfWhich(name,c,r)){.c=>@FieldType(c,name),.r=>@FieldType(r,name),};}inlinefneitherField(comptimename:[]constu8,comptimec:anytype,r:anytype)EitherField(name,@TypeOf(c),@TypeOf(r)){returnswitch(comptimefieldOfWhich(name,@TypeOf(c),@TypeOf(r))){.c=>comptime@field(c,name),.r=>@field(r,name),};}// argument typesconstComptimeArgs=struct{a:comptime_int,};constRuntimeArgs=struct{b:u32,};// calleefnaddComptimeRuntimeIntCast(comptimec:ComptimeArgs,r:RuntimeArgs)u32{consta=eitherField("a",c,r);constb=eitherField("b",c,r);return@intCast(a+b);}// callsitecomptime{if(addComptimeRuntimeIntCast(.{.a=10},.{.b=100})!=110)@compileError("10 + 100 != 110");}

From my previous memory of comptime discussions, I was under the impression that this should be simpler for the compiler to handle (one type fully-runtime, one fully-comptime, fewer edge case behavior with mixed-time types), and remain fully legal and supported even under this proposal.
This seems to work in status-quo, but please let me know if something seems wrong/iffy about this.

Current @call and std.fmt APIs could be supplemented with a second version compatible with a pattern like this - if we want to better support / encourage this. Otherwise the option is still there for userland wrappers.


Even still, here is a counter-proposal for making this more ergonomic in the language itself, in case anyone is interested and has the time to read it (TL;DR: allow declarations in tuples, which can replace status-quo `comptime` fields pretty much 1:1):

.
(hopefully this ended up reasonably short; let me know if it should live in its own proposal suggestion issue.)

The gist of the reasoning behind the proposal seems to be "comptime fields behave more like declarations. That's weird, therefore we want them gone".
Since the two largest use cases both are in tuples, I believe we could solve both of them by simply allowing tuples to contain declarations.

Callsite/provider:

We could make it seemless by keeping the current field syntax. comptime-evaluated values automatically become declarations (instead of comptime fields as in status-quo).
Alternatively, we could require more explicit syntax that reminds of declarations, f.e. something between .{lhs, const rhs, } and .{lhs, const @"1" = rhs, }.

Internals:

There are different ways to model this in @typeInfo and @Tuple.
(EDIT: Change to @typeInfo actually only required under #32047 I think, otherwise only @Tuple should need an interface change.)
The simplest two options I can think of:
(1) Rename fields: []const Field to members: []const Member, where

constMember=struct{type:type,//status-quovalue:*constanyopaque,// status-quo, which I think is this?kind:enum{field,declaration}=.field,//new}.

(2) change type of fields to []const ?Field and add declarations: []const ?Field (and maybe rename Field to Member). Simply assert that for each index exactly one of the options must be non-null.

Callee/consumer:

We can decide whether we want to continue allowing tuple_value.member syntax of tuples to apply to comptime-known members (which are now declarations!), or if we deem this too irregular compared to other aggregates.
In the latter case, the callee/consumer instead needs to use a helper function that checks for @hasDecl/@hasField, and then access via the value or its type. (IMO this would also be totally acceptable, as functions taking anytype often already do some sort of comptime inspection, and it can easily be shimmed-out into a small little helper function.)

This counter-proposal I believe would facilitate keeping all current functionality of @call and std.fmt, with minimal impact on caller and callee code.
That said, maybe even this is deemed unnecessary (in which case I'll probably end up using something like the time-grouping pattern I described above).

I personally have used both `@call` and `std.fmt` with comptime (fields in the tuple) arguments before, and would be slightly annoyed by their direct removal without replacement. <details> <summary>That said, I've already come up with a generic userland wrapper pattern of splitting runtime from `comptime` arguments, which I think would sufficiently supplement the current API:</summary> . The basic idea is to split all arguments into two aggregates (either `struct` or `tuple`), and add a helper function `eitherField("a", c_args, r_args)` to smooth this split over to make it even less obvious in callee code: ```zig // helper function inline fn fieldOfWhich(comptime name: []const u8, comptime C: type, R: type) enum { c, r } { return comptime which: { if (@hasField(C, name)) { if (@hasField(R, name)) @compileError("field '" ++ name ++ "' contained in both argument types '" ++ @typeName(@TypeOf(C)) ++ "' and '" ++ @typeName(@TypeOf(R)) ++ "' (ambiguous)"); break :which .c; } if (!@hasField(R, name)) @compileError("field '" ++ name ++ "' contained in neither argument type '" ++ @typeName(@TypeOf(C)) ++ "' nor '" ++ @typeName(@TypeOf(R)) ++ "'"); break :which .r; }; } fn EitherField(comptime name: []const u8, comptime c: type, comptime r: type) type { return switch (comptime fieldOfWhich(name, c, r)) { .c => @FieldType(c, name), .r => @FieldType(r, name), }; } inline fn eitherField(comptime name: []const u8, comptime c: anytype, r: anytype) EitherField(name, @TypeOf(c), @TypeOf(r)) { return switch (comptime fieldOfWhich(name, @TypeOf(c), @TypeOf(r))) { .c => comptime @field(c, name), .r => @field(r, name), }; } // argument types const ComptimeArgs = struct { a: comptime_int, }; const RuntimeArgs = struct { b: u32, }; // callee fn addComptimeRuntimeIntCast(comptime c: ComptimeArgs, r: RuntimeArgs) u32 { const a = eitherField("a", c, r); const b = eitherField("b", c, r); return @intCast(a + b); } // callsite comptime { if (addComptimeRuntimeIntCast(.{ .a = 10 }, .{ .b = 100 }) != 110) @compileError("10 + 100 != 110"); } ``` From my previous memory of `comptime` discussions, I was under the impression that this should be simpler for the compiler to handle (one type fully-runtime, one fully-`comptime`, fewer edge case behavior with mixed-time types), and remain fully legal and supported even under this proposal. This seems to work in status-quo, but please let me know if something seems wrong/iffy about this. </details> Current `@call` and `std.fmt` APIs could be supplemented with a second version compatible with a pattern like this - if we want to better support / encourage this. Otherwise the option is still there for userland wrappers. ---- <details> <summary>Even still, here is a counter-proposal for making this more ergonomic in the language itself, in case anyone is interested and has the time to read it (TL;DR: allow declarations in tuples, which can replace status-quo `comptime` fields pretty much 1:1):</summary> . (hopefully this ended up reasonably short; let me know if it should live in its own proposal suggestion issue.) The gist of the reasoning behind the proposal seems to be "`comptime` fields behave more like declarations. That's weird, therefore we want them gone". Since the two largest use cases both are in tuples, I believe we could solve both of them by simply allowing tuples to contain declarations. ### Callsite/provider: We could make it seemless by keeping the current field syntax. `comptime`-evaluated values automatically become declarations (instead of `comptime` fields as in status-quo). Alternatively, we could require more explicit syntax that reminds of declarations, f.e. something between `.{lhs, const rhs, }` and `.{lhs, const @"1" = rhs, }`. ### Internals: There are different ways to model this in `@typeInfo` and `@Tuple`. (EDIT: Change to `@typeInfo` actually only required under https://codeberg.org/ziglang/zig/issues/32047 I think, otherwise only `@Tuple` should need an interface change.) The simplest two options I can think of: (1) Rename `fields: []const Field` to `members: []const Member`, where ```zig const Member = struct { type: type, //status-quo value: *const anyopaque, // status-quo, which I think is this? kind: enum {field, declaration} = .field, //new }. ``` (2) change type of `fields` to `[]const ?Field` and add `declarations: []const ?Field` (and maybe rename `Field` to `Member`). Simply assert that for each index exactly one of the options must be non-null. ### Callee/consumer: We can decide whether we want to continue allowing `tuple_value.member` syntax of tuples to apply to `comptime`-known members (which are now declarations!), or if we deem this too irregular compared to other aggregates. In the latter case, the callee/consumer instead needs to use a helper function that checks for `@hasDecl`/`@hasField`, and then access via the value or its type. (IMO this would also be totally acceptable, as functions taking `anytype` often already do some sort of `comptime` inspection, and it can easily be shimmed-out into a small little helper function.) </details> This counter-proposal I believe would facilitate keeping all current functionality of `@call` and `std.fmt`, with minimal impact on caller and callee code. That said, maybe even this is deemed unnecessary (in which case I'll probably end up using something like the time-grouping pattern I described above).
Contributor
Copy link

accessing certain data as if it were a field while using its const-ness to not contribute to the size of the type has been very handy. one example of a way i've been using them is here:
https://git.sr.ht/~nektro/magnolia-desktop/tree/master/item/src/point.zig
https://github.com/nektro/zig-qrcode/blob/master/qrcode.zig#L441
https://github.com/nektro/zig-extras/blob/master/src/RingBuffer.zig

accessing certain data as if it were a field while using its const-ness to not contribute to the size of the type has been very handy. one example of a way i've been using them is here: https://git.sr.ht/~nektro/magnolia-desktop/tree/master/item/src/point.zig https://github.com/nektro/zig-qrcode/blob/master/qrcode.zig#L441 https://github.com/nektro/zig-extras/blob/master/src/RingBuffer.zig

While I do agree that comptime fields are confusing, the confusion often stems from not knowing why something is present. So i think it would be better to inform users/newcomers as to why they need to exist (give example of @call in the documentation) instead of removing them entirely and point out footguns like the pointer being shared across instances of the struct etc...

Here is why i think comptime fields should be kept in the language


Unless tuple support is kept, it would also make it impossible to call functions with a specific modifier. Note that calling the wrapper is not the same thing as calling the function with @call

inlinefnfindAny3Dispatch(hay:[]constu8,start:usize)?usize{return@call(.always_inline,std.mem.indexOfAnyPos,.{u8,hay,start,">'\""});}

I actually do use this in some of my personal projects.

Other projects also use it. zigimg for example also uses it

return@call(if(PNG_DEBUG).autoelse.always_inline,chunkProcessorFn.?,.{self,data});

It also makes it impossible to do things like

fnquerySelector(sel:anytype)!NodeIterator{constfields=@typeInfo(sel).@"struct".fields;constqs=fields[0];// for simplicityif(qs.is_comptime){// query selectors are comptime known most of the timereturn.{.selector=comptimecompileQs(sel.selector,comptimeAllocator)catch{},.gpa=sel.gpa};}else{return.{.selector=trycompileQs(sel,sel.gpa),.gpa=sel.gpa};}}

Although you can implement this using 2 functions, this is more concise


I think this would be simpler compared to @rohlem 's proposal

@Tuple(comptimefield_names:[]const[]constu8,// It's possible to do `const t = .{.named_filed = "value"};`// but not possible to create such a type from current builtins.comptimefield_types:[]consttype,comptimecomptime_known:[]constbool,)type

the reason is, converting fields to declarations breaks the access pattern entirely; we'd have to do @TypeOf(v).f instead of v.f

or just have tuple as layout for struct instead of a separate type; this makes language simpler rather than having separate types for structs and tuples.

While I do agree that comptime fields are confusing, the confusion often stems from not knowing **why** something is present. So i think it would be better to inform users/newcomers as to why they need to exist (give example of `@call` in the documentation) instead of removing them entirely and point out footguns like the pointer being shared across instances of the struct etc... Here is why i think comptime fields should be kept in the language --- Unless tuple support is kept, it would also make it impossible to call functions with a specific modifier. Note that calling the wrapper is not the same thing as calling the function with `@call` ```zig inline fn findAny3Dispatch(hay: []const u8, start: usize) ?usize { return @call(.always_inline, std.mem.indexOfAnyPos, .{ u8, hay, start, ">'\"" }); } ``` I actually do use this in some of my personal [projects](https://github.com/SmallThingz/zhtml/blob/5bff890cf9c63ffcced73672230f4a5ae5333ad3/src/html/scanner.zig#L192-L194). Other projects also use it. zigimg for example [also uses it](https://github.com/zigimg/zigimg/blob/c7e81a13bff1cf4c5b7f085a8360f9d551143d40/src/formats/png/reader.zig#L671-L682) ```zig return @call(if (PNG_DEBUG) .auto else .always_inline, chunkProcessorFn.?, .{ self, data }); ``` --- It also makes it impossible to do things like ```zig fn querySelector(sel: anytype) !NodeIterator { const fields = @typeInfo(sel).@"struct".fields; const qs = fields[0]; // for simplicity if (qs.is_comptime) { // query selectors are comptime known most of the time return .{.selector = comptime compileQs(sel.selector, comptimeAllocator) catch {}, .gpa = sel.gpa}; } else { return .{.selector = try compileQs(sel, sel.gpa), .gpa = sel.gpa}; } } ``` Although you can implement this using 2 functions, this is more concise --- I think this would be simpler compared to @rohlem 's proposal ```zig @Tuple( comptime field_names: []const []const u8, // It's possible to do `const t = .{.named_filed = "value"};` // but not possible to create such a type from current builtins. comptime field_types: []const type, comptime comptime_known: []const bool, ) type ``` the reason is, converting fields to declarations breaks the access pattern entirely; we'd have to do @TypeOf(v).f instead of v.f or just have tuple as layout for struct instead of a separate type; this makes language simpler rather than having separate types for structs and tuples.
Contributor
Copy link

To answer @castholm, here's a userland snippet which detects whether some value is comptime-known, without using comptime fields:

pubinlinefncomptimeKnown(x:anytype)bool{constpair:struct{@TypeOf(x),usize}=.{x,0};return@TypeOf(" "[0..pair[1]])==*const[0]u8;}testcomptimeKnown{constx:i32=1;trystd.testing.expect(comptimeKnown(x));vary:i32=1;_=&y;trystd.testing.expect(!comptimeKnown(y));}
To answer @castholm, here's a userland snippet which detects whether some value is `comptime`-known, without using `comptime` fields: ```zig pub inline fn comptimeKnown(x: anytype) bool { const pair: struct { @TypeOf(x), usize } = .{ x, 0 }; return @TypeOf(" "[0..pair[1]]) == *const [0]u8; } test comptimeKnown { const x: i32 = 1; try std.testing.expect(comptimeKnown(x)); var y: i32 = 1; _ = &y; try std.testing.expect(!comptimeKnown(y)); } ```

While I fully share the opinion that "comptime" is a counter-intuitive term (I faced the same confusion when I was a beginner), personally, I don't see any real benefit for language users in this proposal.

Firstly, regarding the provided example with the wrapped @call: it only suits one specific generic case. It cannot be done (or would be very difficult) with an arbitrary function with an unknown number of arguments (I explain below why that is important to me). Moreover, it harms "easy to read & understand" ergonomics.

Secondly, comptime fields are a good spot for optimizations (and not just for formatting). The common case in modern gamedev is a system-based approach (like Bevy in Rust or Mach in Zig), where systems are registered at comptime, analyzed during comptime, and have their arguments set at runtime. In Zig, this can be done as a zero-cost runtime operation. It will continue to work if this proposal is implemented, unless you encounter an argument with a comptime-only type that must be instantiated during the creation of an ArgsTuple type. In my in-dev engine, such types are structs with function bodies (I know all field values at comptime, so I can create such structs at comptime, which is quite nice), and I use a custom ArgsTuple implementation that handles this case.

I can imagine a workaround for the case described above that looks something like this (pseudocode):

switch(comptimeargs.len){1=>system(gimmeTheArg(args[0])),2=>system(gimmeTheArg(args[0]),gimmeTheArg(args[1])),else=>@compileError("systems with more than 2 args are unsupported"),}

So, my counter-proposal is:

  1. Remove comptime fields from structs but keep them in tuples. In structs, comptime fields can be replaced with constant declarations without any loss of functionality.

  2. Rename "comptime fields" to "const fields" to remove the confusing concept (yes, unfortunately, this might cause confusion with const declarations, so it might be worth considering other naming variants).

  3. Change the @Tuple builtin signature to:

@Tuple(types:[]consttype,values:*const[types.len]?std.builtin.Type.DefaultValue,// if value is null, that means the field is not const)

To summarize, from my perspective, it would be desirable to keep comptime fields and tuples in the language (ideally treating tuples as a distinct type, strictly separate from structs).

While I fully share the opinion that "comptime" is a counter-intuitive term (I faced the same confusion when I was a beginner), personally, I don't see any real benefit for language users in this proposal. Firstly, regarding the provided example with the wrapped `@call`: it only suits one specific generic case. It cannot be done (or would be very difficult) with an arbitrary function with **an unknown number of arguments** (I explain below why that is important to me). Moreover, it harms "easy to read & understand" ergonomics. Secondly, comptime fields are [a good spot for optimizations](https://github.com/ziglang/zig/issues/19483#issuecomment-2032695648) (and not just for formatting). The common case in modern gamedev is a system-based approach (like Bevy in Rust or Mach in Zig), where systems are registered at comptime, analyzed during comptime, and have their arguments set at runtime. In Zig, this can be done as a zero-cost runtime operation. It will continue to work if this proposal is implemented, unless you encounter an argument with a comptime-only type that must be instantiated during the creation of an `ArgsTuple` type. In my in-dev engine, such types are structs with function bodies (I know all field values at comptime, so I can create such structs at comptime, which is quite nice), and I use a custom `ArgsTuple` implementation that handles this case. I can imagine a workaround for the case described above that looks something like this (pseudocode): ```zig switch (comptime args.len) { 1 => system(gimmeTheArg(args[0])), 2 => system(gimmeTheArg(args[0]), gimmeTheArg(args[1])), else => @compileError("systems with more than 2 args are unsupported"), } ``` So, my counter-proposal is: 1. Remove comptime fields from structs but keep them in tuples. In structs, comptime fields can be replaced with constant declarations without any loss of functionality. 2. Rename "comptime fields" to "const fields" to remove the confusing concept (yes, unfortunately, this might cause confusion with const declarations, so it might be worth considering other naming variants). 3. Change the `@Tuple` builtin signature to: ```zig @Tuple( types: []const type, values: *const [types.len]?std.builtin.Type.DefaultValue, // if value is null, that means the field is not const ) ``` To summarize, from my perspective, it would be desirable to keep comptime fields and tuples in the language (ideally treating tuples as a distinct type, strictly separate from structs).

Perhaps this proposal can be implemented without causing any use cases to become impossible by introducing singleton types.

A comptime known value can coerce to it's singleton type

constsingleton_u32:@Singleton(type,u32)=u32;

A singletons value field is comptime known even if the singleton itself is only runtime known.
The same as the len field of an array.

std.debug.assert(singleton_u32.value==u32)

A singleton can coerce to its value

consta:type=singleton_u32

Comptime known values put into a runtime tuple, become their singletons

fnfoo(args:anytype)void{}foo(.{u8,5})

foo recieves @Tuple(&.{@Singleton(type, u8), @Singleton(comptime_int, 5)})
Singletons can then be processed to perform any optimizing logic, or to pass comptime arguments to functions.

Perhaps this proposal can be implemented without causing any use cases to become impossible by introducing singleton types. A comptime known value can coerce to it's singleton type ```zig const singleton_u32: @Singleton(type, u32) = u32; ``` A singletons `value` field is comptime known even if the singleton itself is only runtime known. The same as the `len` field of an array. ```zig std.debug.assert(singleton_u32.value == u32) ``` A singleton can coerce to its value ```zig const a: type = singleton_u32 ``` Comptime known values put into a runtime tuple, become their singletons ```zig fn foo(args: anytype) void {} foo(.{u8, 5}) ``` `foo` recieves `@Tuple(&.{@Singleton(type, u8), @Singleton(comptime_int, 5)})` Singletons can then be processed to perform any optimizing logic, or to pass comptime arguments to functions.
Contributor
Copy link

This proposal makes it impossible to directly use @call at runtime on functions with comptime parameters

This effectively make function with comptime parameters second class citizens.
Those kind of functions are very important to control codegen, to make sure the compiler has all the required information to compile very specialized version of the code.

I also find "comptime field" weird, but it's IMO an implementation details of how @call works. I'm in favor of simplyfying the spec and implem, but being able to reason on a function arguments and passing those arguments around to call is central to Zig comptime capabilities.

(...okay, well, apparently right now it actually crashes the compiler! Sorry about that---although I think the fact that not many people have hit this bug reinforces the argument that this is not a common use case.)

This is literally the first thing I tried on 0.16. Our version of async implemented on 0.15 supported it so I immediately checked how Zig async handled comptime and anytype (not great IMO).

> This proposal makes it impossible to directly use @call at runtime on functions with comptime parameters This effectively make function with comptime parameters second class citizens. Those kind of functions are very important to control codegen, to make sure the compiler has all the required information to compile very specialized version of the code. I also find "comptime field" weird, but it's IMO an implementation details of how `@call` works. I'm in favor of simplyfying the spec and implem, but being able to reason on a function arguments and passing those arguments around to call is central to Zig comptime capabilities. > (...okay, well, apparently right now it actually crashes the compiler! Sorry about that---although I think the fact that not many people have hit this bug reinforces the argument that this is not a common use case.) This is literally the first thing I tried on 0.16. Our version of async implemented on 0.15 supported it so I immediately checked how Zig async handled comptime and anytype (not great IMO).

I might be misunderstanding the problem, but if the crux of the issue is that comptime fields behave more like declarations, then why not make them more like fields that happen to be known at comptime? They would appear in runtime memory, solving all those pain points. I have a feeling that this wouldn't work, otherwise it would've been considered. Is this incompatible with how comptime works?

I might be misunderstanding the problem, but if the crux of the issue is that comptime fields behave more like declarations, then why not make them more like fields that happen to be known at comptime? They would appear in runtime memory, solving all those pain points. I have a feeling that this wouldn't work, otherwise it would've been considered. Is this incompatible with how comptime works?

why not make them more like fields that happen to be known at comptime?

@masabence I think the biggest issue here is that they're supposed to allow comptime-only types like comptime_int or type, which have no known size nor runtime representation.
(Additionally most use cases would want to reduce runtime size, although honestly that part alone might be an acceptable trade-off.)

> [why not make them more like fields that happen to be known at comptime?](https://codeberg.org/ziglang/zig/issues/32045#issuecomment-13946501) @masabence I think the biggest issue here is that they're supposed to allow `comptime`-only types like `comptime_int` or `type`, which have no known size nor runtime representation. (Additionally most use cases would want to reduce runtime size, although honestly that part alone might be an acceptable trade-off.)

Comptime fields seem justified on the principle that the type system should mirror capabilities given to you/asked of you by the rest of the language. If a function takes arguments or returns in special way, the user shouldn't be stuck without a way to construct a type that meets these requirements. In other words, because you can write a function that takes comptime arguments:

pubfnfoo(comptimeT:type,x:T)U{...}

it should also be possible to have structs or tuples with comptime fields. While I agree that comptime fields are troublesome, and a tough hurdle to get over when first learning the language, I don't think breaking with this pattern is worth it -- unless the intention here is to remove comptime arguments as well.

Comptime fields seem justified on the principle that the type system should mirror capabilities given to you/asked of you by the rest of the language. If a function takes arguments or returns in special way, the user shouldn't be stuck without a way to construct a type that meets these requirements. In other words, because you can write a function that takes `comptime` arguments: ```zig pub fn foo(comptime T: type, x: T) U { ... } ``` it should also be possible to have structs or tuples with `comptime` fields. While I agree that comptime fields are troublesome, and a tough hurdle to get over when first learning the language, I don't think breaking with this pattern is worth it -- unless the intention here is to remove `comptime` arguments as well.
Contributor
Copy link

If the goal of compiler team is to simplify Tuples, and to promote their usage, give us a "FnArgs" thingy that can capture all the comptime and runtime arguments of a function, and remove comptime fields elsewhere.

If the goal of the compiler team is to restrict tuple usage, keep them just for storing fn args and therefore with comptime field or equivalent.

If the goal of compiler team is to simplify Tuples, and to promote their usage, give us a "FnArgs" thingy that can capture all the comptime and runtime arguments of a function, and remove comptime fields elsewhere. If the goal of the compiler team is to restrict tuple usage, keep them just for storing fn args and therefore with comptime field or equivalent.

I'm very much a newcomer to Zig, and am still learning how comptime works. In the section of the Zig language reference explaining comptime, it has subsections on compile-time parameters, variables, and expressions, but not compile-time fields. If comptime fields are worth keeping in the language, then shouldn't they get a mention here?

In my opinion, requiring users to capture comptime parameters with a closure feels intuitive, and I think it meshes well with Zig's design philosophy. It makes very explicit the difference between how compile-time and run-time values can be used.

I'm very much a newcomer to Zig, and am still learning how comptime works. In the section of the Zig language reference explaining comptime, it has subsections on compile-time [parameters](https://ziglang.org/documentation/master/#Compile-Time-Parameters), [variables](https://ziglang.org/documentation/master/#Compile-Time-Variables), and [expressions](https://ziglang.org/documentation/master/#Compile-Time-Expressions), but not compile-time fields. If comptime fields are worth keeping in the language, then shouldn't they get a mention here? In my opinion, requiring users to capture comptime parameters with a closure feels intuitive, and I think it meshes well with Zig's design philosophy. It makes very explicit the difference between how compile-time and run-time values can be used.

From a user's perspective, I would like to use comptime anywhere of course.
Being able to mix comptime and runtime values is a great advantage, it takes the burden of thinking what can be comptime or not and just allow user to experiment with the fields.

Can we add a optimize pass to eliminate comptime from struct/tuple and fill in the literal value where it's used? (Sorry in advance if this is not possible/near impossible as I don't fully understand compilers.)

From a user's perspective, I would like to use comptime anywhere of course. Being able to mix comptime and runtime values is a great advantage, it takes the burden of thinking what can be comptime or not and just allow user to experiment with the fields. Can we add a optimize pass to eliminate comptime from struct/tuple and fill in the literal value where it's used? (Sorry in advance if this is not possible/near impossible as I don't fully understand compilers.)

I think it's clear that removing comptime fields from the language should be done at some point.
It is honestly a weird quirk that does not pull its own weight in my opinion.

A comptime field is a value that lives in the type, not in any instance; semantically this is identical to a declaration on the type.

These benefits follow from @mlugg's proposal (I would think):

  1. The is_comptime: bool field on StructField goes away.
  2. The @"comptime": bool field on StructField.Attributes goes away.
  3. The grammar production that allows comptime before a field in a struct goes away.
  4. The invalid pointer semantics &s.comptime_field goes away.
  5. The layout-rule exception "comptime fields don't contribute to alignment or size" goes away since there are no such fields.
  6. Other issues that exist downstream of comptime fields are eliminated.

After the proposal, StructField looks like (assumed):

pubconstStructField=struct{name:[:0]constu8,type:type,default_value_ptr:?*constanyopaque,// non-tuple onlyalignment:?usize,pubconstAttributes=struct{@"align":?usize=null,default_value_ptr:?*constanyopaque=null,};};

Now every field in every struct type represents a real runtime slot.
I am unsure about the internal byte savings, but the real win is removing a branch from every path that touches struct fields.

! But we should want to preserve the print and @call use cases without user-side rewrites, and perhaps it is possible:

// keep this working without user rewritesconstx:u32=42;std.debug.print("value {d} of type {any}\n",.{x,@TypeOf(x)});

Since the compiler already determines whether each tuple slot is comptime-known (I assume how is_comptime is set today), why not use that same classification but route the result differently? Slots with comptime-only types go into decls, runtime slots into fields, both keyed by positional name ("0", "1", ...).
The expressiveness of mixed-staging tuples is preserved; but there are changes to the internal bucketing.

Now, to keep the expressiveness we expect, some adjustments are needed (those I can think of):

  • tuple.len needs to equate fields.len + decls.len so positional iteration covers every slot.
  • tuple[i] and tuple.@"i" already go through name resolution; that resolution must now consult both fields and decls, returning a runtime load or a comptime substitution accordingly.
  • inline for (@typeInfo(T).@"struct".fields) no longer walks every tuple slot (only the runtime ones), so code relying on this pattern needs to walk a unified slot enumeration. A helper like std.meta.tupleSlots() can cover the common case.
  • An anonymous tuple/struct's identity today includes the comptime field values via default_value_ptr​, but the proposed change would void this fact, @TypeOf(.{4}) == @TypeOf(.{3}) would become true - without any changes to how identity is derived.
// ReminderpubconstStruct=struct{layout:ContainerLayout,backing_integer:?type=null,fields:[]constStructField,decls:[]constDeclaration,is_tuple:bool,};

I have limited understanding of the compiler, so it might not be this straight forward, or even accurate, please correct me.
There is probably more coverage that goes into it.

I think it's clear that removing comptime fields from the language should be done at some point. It is honestly a weird quirk that does not pull its own weight in my opinion. A comptime field is a value that lives in the *type*, not in any instance; semantically this is identical to a declaration on the type. These benefits follow from @mlugg's proposal (I would think): 1. The `is_comptime: bool` field on StructField goes away. 2. The `@"comptime": bool` field on StructField.Attributes goes away. 3. The grammar production that allows `comptime` before a field in a struct goes away. 4. The invalid pointer semantics `&s.comptime_field` goes away. 5. The layout-rule exception "comptime fields don't contribute to alignment or size" goes away since there are no such fields. 6. Other issues that exist downstream of comptime fields are eliminated. After the proposal, `StructField` looks like (assumed): ```zig pub const StructField = struct { name: [:0]const u8, type: type, default_value_ptr: ?*const anyopaque, // non-tuple only alignment: ?usize, pub const Attributes = struct { @"align": ?usize = null, default_value_ptr: ?*const anyopaque = null, }; }; ``` Now every field in every struct type represents a real runtime slot. I am unsure about the internal byte savings, but the real win is removing a branch from every path that touches struct fields. ! But we should want to preserve the `print` and `@call` use cases without user-side rewrites, and perhaps it is possible: ```zig // keep this working without user rewrites const x: u32 = 42; std.debug.print("value {d} of type {any}\n", .{ x, @TypeOf(x) }); ``` Since the compiler already determines whether each tuple slot is comptime-known (I assume how `is_comptime` is set today), why not use that same classification but route the result differently? Slots with comptime-only types go into `decls`, runtime slots into `fields`, both keyed by positional name ("0", "1", ...). The expressiveness of mixed-staging tuples is preserved; but there are changes to the internal bucketing. Now, to keep the expressiveness we expect, some adjustments are needed (those I can think of): - `tuple.len` needs to equate `fields.len + decls.len` so positional iteration covers every slot. - `tuple[i]` and `tuple.@"i"` already go through name resolution; that resolution must now consult both `fields` and `decls`, returning a runtime load or a comptime substitution accordingly. - `inline for (@typeInfo(T).@"struct".fields)` no longer walks every tuple slot (only the runtime ones), so code relying on this pattern needs to walk a unified slot enumeration. A helper like `std.meta.tupleSlots()` can cover the common case. - An anonymous tuple/struct's identity today includes the comptime field values via `default_value_ptr`​, but the proposed change would void this fact, `@TypeOf(.{4}) == @TypeOf(.{3})` would become *true* - without any changes to how identity is derived. ```zig // Reminder pub const Struct = struct { layout: ContainerLayout, backing_integer: ?type = null, fields: []const StructField, decls: []const Declaration, is_tuple: bool, }; ``` I have limited understanding of the compiler, so it might not be this straight forward, or even accurate, please correct me. There is probably more coverage that goes into it.
Sign in to join this conversation.
No Branch/Tag specified
master
elfv2
spork8
restricted
0.16.x
panic-rewrite
parking-futex-lockfree
windows-Io-cleanup
Io-watch
ParseCommandLineOptions
windows-async-files
poll-ring
debug-file-leaks-differently
debug-file-leaks
ProcessPrng
elfv2-dyn
jobserver
threadtheft
io-threaded-no-queue
0.15.x
Io.net
comptime-allocator
restricted-function-pointers
cli
wasm-linker-writer
wrangle-writer-buffering
sha1-stream
async-await-demo
fixes
0.14.x
ast-node-methods
macos-debug-info
make-vs-configure
fuzz-macos
sans-aro
ArrayList-reserve
incr-bug
llvm-ir-nosanitize-metadata
ci-tarballs
ci-scripts
threadpool
0.12.x
new-pkg-hash
json-diagnostics
more-doctests
rework-comptime-mutation
0.11.x
ci-perf-comment
stage2-async
0.10.x
autofix
0.9.x
aro
hcs
0.8.x
0.7.x
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.12.1
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
Labels
Clear labels
abi/f32
abi/ilp32
abi/sf
accepted
This proposal is planned.
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
The web application for interactive documentation and generation of its assets.
backend/c
The C backend outputs C source code.
backend/llvm
The LLVM backend outputs an LLVM bitcode module.
backend/self-hosted
The self-hosted backends produce machine code directly.
binutils
Zig's included binary utilities: zig ar, zig dlltool, zig lib, zig ranlib, zig objcopy, and zig rc.
breaking
Implementing this issue could cause existing code to no longer compile or have different behavior.
build system
The Zig build system - zig build, std.Build, the build runner, and package management.
debug info
An issue related to debug information (e.g. DWARF) produced by the Zig compiler.
docs
An issue with documentation, e.g. the language reference or standard library doc comments.
error message
This issue points out an error message that is unhelpful and should be improved.
frontend
Tokenization, parsing, AstGen, ZonGen, Sema, Legalize, and Liveness.
fuzzing
An issue related to Zig's integrated fuzz testing.
incremental
Reuse of internal compiler state for faster compilation.
lib/c
This issue relates to Zig's libc implementation and/or vendored libcs.
lib/compiler-rt
This issue relates to Zig's compiler-rt library.
lib/cxx
This issue relates to Zig's vendored libc++ and/or libc++abi.
lib/std
This issue relates to Zig's standard library.
lib/tsan
This issue relates to Zig's vendored libtsan.
lib/ubsan-rt
This issue relates to Zig's ubsan-rt library.
lib/unwind
This issue relates to Zig's vendored libunwind.
linking
Zig's integrated object file and incremental linker.
miscompilation
The compiler reports success but produces semantically incorrect code.
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
This issue suggests language modifications. If it also has the "accepted" label then it is planned.
release notes
This issue or pull request should be mentioned in the release notes.
testing
This issue is related to testing the compiler, standard library, or other parts of Zig.
zig cc
Zig as a drop-in C-family compiler.
zig fmt
The Zig source code formatter.
zig reduce
The Zig source code reduction tool.
bounty
https://ziglang.org/news/announcing-donor-bounties
bug
Observed behavior contradicts documented or intended behavior.
contributor-friendly
This issue is limited in scope and/or knowledge of project internals.
downstream
An issue with a third-party project that uses this project.
enhancement
Solving this issue will likely involve adding new logic or components to the codebase.
infra
An issue related to project infrastructure, e.g. continuous integration.
optimization
A task to improve performance and/or resource usage.
question
No questions on the issue tracker; use a community space instead.
regression
Something that used to work in a previous version stopped working
upstream
An issue with a third-party project that this project uses.
use case
Describes a real use case that is difficult or impossible, but does not propose a solution.
No labels
abi/f32
abi/ilp32
abi/sf
accepted
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
backend/c
backend/llvm
backend/self-hosted
binutils
breaking
build system
debug info
docs
error message
frontend
fuzzing
incremental
lib/c
lib/compiler-rt
lib/cxx
lib/std
lib/tsan
lib/ubsan-rt
lib/unwind
linking
miscompilation
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
release notes
testing
zig cc
zig fmt
zig reduce
bounty
bug
contributor-friendly
downstream
enhancement
infra
optimization
question
regression
upstream
use case
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
14 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ziglang/zig#32045
Reference in a new issue
ziglang/zig
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?