I don’t understand how or why S is shared across invocations of foo? Reading the example code I would have thought that every invocation creates a new instance of the struct S? That S would be destroyed at the end of the function scope as it would be part of foo’s stack frame?
Please explain it like you’re explaining it to someone that has been writing Typescript for the last 9 years, python before that, and a career spent trying to forget everything I learned about java
I’m really hungry for a deep, bottom-up, understanding here – I’m learning zig because, amongst other things, I want full control over memory, and this example breaks my current mental model of stack allocation (which is pretty exciting tbh ).
Here’s a theory that is ~probably~ definitely wrong: since a test declaration is effectively a function, maybe S is hoisted up to the test declaration’s stack frame? I add created two test declarations to test this, and found that the state is shared across test declarations. Hmm Yeah my current mental model is swiss cheese
Because it is a global variable. It is global because it is declared in the root of a type. The type being declared in a local function scope is irrelevant.
Btw files are implicit struct’s, so it is literally identical to declaring a global in the root of a file, as far as the language is concerned.
No, it is identical. In both cases, S exists as a type, and could even be used outside the function if you could access it.
The only difference is where it is defined, and by extension whether you can access it elsewhere.
also, in your post you seem to have thought x was a field, at least the way you described how you expected it to work would be that of a field in zig. x is not a field in this case, it is a global declaration.
I did edit in an explanation of your misunderstanding:
the way you would define a field is like this:
const S = struct {
// no const, no var
x: i32,
};
if its a field then you’d have to make an instance in order to access x which you didnt do until now.
so unless you used a global instance, it would not persist across invokations.
and that makes the whole “a module is just an implicit struct“ concept really click! if I can declare a variable inside a file, then I can declare a variable inside a struct in exactly the same way, with the same semantics
now that I finally understand that, I can see how useful that is going to be!
what you might still be missing on that concept, is you can also decalre fields in a file, and instantiate it like any other struct.
see std.Io.Reader as the first real world example that came to mind.
but also a simpler example:
x: i32,
// @This() allows you to refer to a type when you cant refer to its name
// the type still has a name, in this case it will be the files name
// also useful for generic types
const zero: @This() = .{ .x = 2 };
// you could also make an alias
// It's better practice to use the real name of the type
// in this case the file name, but thats not enforced.
const Self = @This();
This is a very minds breaking trick of zig, when migrating from C world (in my case). But it helps to write less code, despite the fact that zig discourages such practices. Check out zig sources, they use it everywhere, making it state of practice in zig programming (at least imho).