While working on a program, I accidentally declared the variables in a struct incorrectly. The strange thing is that it actually worked and I'm not entirely sure why. I'm submitting this question to hopefully get some answers as to why this compiles and what the program is actually doing.
Here's the code of a short example program I made:
const std = @import("std");
//Standard Struct
const Regular = struct {
message: [:0]const u8 = undefined,
counter: u8 = 0,
pub fn init(self: *Regular) void {
self.message = "Hello from Regular";
self.incrementCounter();
}
fn incrementCounter(self: *Regular) void {
self.counter += 1;
}
pub fn print(self: *Regular) void {
std.debug.print("Counter: {}, Message: {s}\n", .{ self.counter, self.message });
}
};
//Weird Struct
const Weird = struct {
var message: [:0]const u8 = undefined;
var counter: u8 = 0;
pub fn init() void {
message = "Hello from Weird";
incrementCounter();
}
fn incrementCounter() void {
counter += 1;
}
pub fn print() void {
std.debug.print("Counter: {}, Message: {s}\n", .{ counter, message });
}
};
pub fn main() !void {
var a = Regular{};
const b = Weird;
a.init();
b.init();
a.print();
b.print();
var counter: u8 = 0;
while (counter < 20) : (counter += 1) {
a.incrementCounter();
b.incrementCounter();
}
a.print();
b.print();
}
Output:
Counter: 1, Message: Hello from Regular
Counter: 1, Message: Hello from Weird
Counter: 21, Message: Hello from Regular
Counter: 21, Message: Hello from Weird
Regular matches the standard struct architecture I've found in most examples from the documentation as well as Ziglings. Weird is this thing I accidentally made. Somehow this is still valid Zig and runs as I would expect, but there are a few quirks I don't quite understand.
For example, b cannot be a variable. If I attempt it, I get the error: "Error: variable of type 'type' must be const or comptime". Which is odd, since I'm expecting the type of b to be filename.Weird. Otherwise, it works how I would expect it. In the original program I discovered this in, I only discovered this by having to use a pointer to a Weird-type struct and finding numerous errors until I refactored it to a Regular-type struct. So what exactly is happening in Weird? Why does it work? How does it work?
Inside Weird
message
and counter
are container level variables. This works for you here because the struct methods in Weird
are using the container level variables (but it may not always work as you expect as described below). You can access these from the outside through the Weird
type, e.g., Weird.message
and Weird.counter
.
You are getting an error with var b = Weird;
because Weird
is a type, and types aren't available at runtime, so you have to use const
or comptime
to assign the type to an identifier.
This differs from var a = Regular{};
which creates an instance of the Regular
struct.
In the posted code a
is a Regular
struct, while b
is the struct type Weird
. The ramifications of this are that a.incrementCounter()
is incrementing the counter
field of the struct a
which is of type Regular
, and there could be many independent instances of Regular
structs. But b.incrementCounter()
is incrementing the container level variable Weird.counter
. This counter
is static, and there is only one instance of it.
If you add these lines to the end of the posted program:
const c = Weird;
c.init();
c.message = "This is a container level message!";
c.print();
b.print();
and then compile and run the program, this is the output:
$ zig run container_variables.zig
Counter: 1, Message: Hello from Regular
Counter: 1, Message: Hello from Weird
Counter: 21, Message: Hello from Regular
Counter: 21, Message: Hello from Weird
Counter: 22, Message: This is a container level message!
Counter: 22, Message: This is a container level message!
Here c
is bound to the type Weird
just as b
is. Calling c.init()
increments the container level variable Weird.counter
and c.message = ...
assigns a new string message to Weird.counter
. Subsequent calls to b.print
, c.print
, or Weird.print
will all show the same results since this method is printing the values of container level variables.