yontuh
November 5, 2024, 5:54am
1
This doesn’t compile… Why? And why does this raylib-zig example compile?
const std = @import("std");
pub fn main() anyerror!void {
var player = .{ .x = 400, .y = 280, .width = 40, .height = 40 };
player.x += 2;
std.debug.print("{any}", .{player});
}
Error:
error: value stored in comptime field does not match the default value of the field
player.x += 2;
Error that should be showing the types (this error I included for the struct output)
error: incompatible types: 'struct{comptime x: comptime_int = 70, comptime y: comptime_int = 35, comptime width: comptime_int = 20, comptime height: comptime_int = 20}' and 'comptime_int'
player += 1;
comptime x: comptime_int = 70
Why did the compiler decide to implement comptime?
Does the double comptime mean anything? (comptime of the variable vs the integer itself)
I understand that, comptime is a way for the code to modify itself for optimizations & security, for example enabling or disabling code for different OSs but I don’t understand why it is changing the types of the struct values, especially because it is mutable.
kj4tmp
November 5, 2024, 6:06am
2
Welcome to ziggit!!
I believe you have created an anonymous struct type. Which I think has been removed from the language (master branch).
opened 10:59AM - 17 Aug 23 UTC
closed 01:43AM - 01 Nov 24 UTC
proposal
accepted
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:
```zig
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](/ziglang/zig/issues/16862)
* 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` or `struct_init_anon_ref`)
* The number of those anonymous struct inits which would remain if #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.
You can fix your code by creating a type for player
:
const Player = struct {
x: u16,
y: u16,
width: u8,
height: u8,
}
pub fn main() anyerror!void {
var player: Player = .{ .x = 400, .y = 280, .width = 40, .height = 40 };
player.x += 2;
std.debug.print("{any}", .{player});
}
Because the raylib example explicitely states the type of the variable to be rl.Rectangle
, which itself has explicitely defined integer types for its struct members. In your anonymous struct (as pointed out by @kj4tmp ), there are no explicit types given, so the compiler chooses comptime_int.