I’m just curious why I can’t do something like have a variable that is defined at comptime but still a variable? As long as it’s only modified after comptime it shouldn’t matter that it is a var, but the compiler won’t let me do that. Also, why does the compiler stop me from explicitly defining things as comptime if it already inferred it? Seems like there are other restrictions on what can and can’t be done at comptime (like i/o? that seems like it could be valuable), and most of what is possible is aimed at type reflection and defining target-dependent conditions.
So why aren’t we doing more with comptime, what am I missing?
Compilers are indeed capable of making many inferences and optimizations; the comptime keyword exists to enable rigorous runtime resource planning without relying on compiler automation.
If you want more flexibility, you can use @inComptime() instead of comptime.
It looks like you are simply seeking the convenience of code reuse specifically, avoiding the need to write the same initial value twice, but this actually represents a conflation of concepts that runs counter to the purpose of the comptime keyword.
That’s a mutable variable initialized with a value computed at comptime.
I’m guessing you’re referring to comptime var, but that’s a different thing and the compiler purposefully restricts the “mutability window” of such variables to keep comptime a sound abstraction. If you do need a comptime var then, once you’re done mutating it, copy the final value out to a const declaration in a wider lexical scope.
// in a comptime context
const foo = blk: {
comptime var foo = 0;
foo += 1; // or whatever mutation is necessary
break :blk foo;
};
That said, your post doesn’t seem refer to anything that requires this technique, so maybe you just want a normal var?
That makes a lot of sense. I hadn’t thought of that as “code reuse” but it’s a useful framing.
I apologize if that was vague:
I was attempting a global var for a space invaders clone I wrote as a learning project. Version 0.16.
I wanted to set movement speeds as a global var, but also derive them at comptime based on screen size (yes, there are a few other problems there, lol). The incorrect version I expected would have looked roughly like:
comptime var invader_speed: f32 = screen_width / 8;
// I used relative math for everything so I could change resolutions
fn kill_entity(foo) void {
foo.init();
invader_speed += invader_speed_increment
I did end up just defining my value as a const at comptime and copying it to a global var I could mutate at runtime:
const invader_start_speed = doMath();
var invader_speed = invader_start_speed
The part I don’t understand is why that’s preferable specifically.
I think I am conflating some other ideas like scopes and closures into my expectations and assumptions. What I originally imagined comptime to look like was ~=
comptime{//everything here happens at compile time}
//normal code, maybe inferred at comptime as a compiler optimization
so when the compiler tells me that it can’t infer something at compile time my first instinct is “I should be able to just tell it what it needs to know”. I understand that is not how it works, but not why, or maybe even why I might not actually want to be able to do that.
There are multiple reasons why comptime doesn’t work like this, such that mutable global state makes it much more difficult for readers to reason about a piece of code and understand what it does. With the current rules, if you see pub const foo = 1, you as a reader know that foo will always be 1, and if you see pub var bar = 2, you know that bar cannot affect comptime state and can only be referenced at runtime. If pub var bar = 2 was allowed to be mutated at comptime, you might end up in a situation where you do const utils = @import("utils.zig") and now suddenly bar is reassigned to 3 because utils.zig had a comptime block that reassigned it, and as a result it will require much more effort from you to develop a mental model of your program and hold it within your head.
Most software developers consider it good practice to avoid global state that is mutable at runtime precisely because it is difficult to reason about. If we want to write useful programs we can’t do away with mutable global runtime state entirely (despite what functional programming snake oil salesmen would want to have you believe ), but we do have the freedom of removing mutable global comptime state.
However, I suspect that the main practical reason Zig doesn’t allow mutable global comptime state is that allowing it would make compilation much slower and more complicated, and likely gimp incremental compilation if not make it outright impossible to implement (incremental is a very important component of Zig’s sales pitch). By limiting mutable comptime state to clearly scoped blocks, the compiler is free to do a lot more work in parallel. Had mutable global comptime state been allowed, the compiler would likely need to process each file sequentially in a deterministic order, which would have been a lot slower by multiple factors.