I’m trying to understand the syntax for declaring allocators, but it’s a bit confusing to me. For example, I’ve seen code like this:
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
Here, std.heap.GeneralPurposeAllocator(.{}) looks like a function call being used in place of a type, but in simpler declarations, like var num: i32 = 22;, the type is just plainly i32 without any call-like syntax.
Why do we use this “function call” style for the allocator type? Is GeneralPurposeAllocator not a direct type, but something generated at compile time? I’d appreciate an explanation of what’s going on under the hood and why it’s designed this way.
Not sure which version of zig you are using, but GeneralPurposeAllocator has been renamed to DebugAllocator.
this is the signature pub fn DebugAllocator(comptime config: Config) type
it takes a comptime config and returns a type.
This is how generic types are done in zig, functions that create a type from the args.
DebugAllocator(.{}) calls it with the default config.
and .init is: pub const init: Self = .{};, a constant which is the default value of the type.
in the future, the default field values will be removed, and init will be a non default instantiation.
This is because its possible to create an invalid instance of the type if you set some fields while leaving others the default, so types in std are moving away from that.
You can tell types and functions apart by their names. Functions start with lower case letter while types start with upper case letters. Language reference says:
If x is callable, and x’s return type is type, then x should be TitleCase.
If x is otherwise callable, then x should be camelCase.
//original
items: Slice = &[_]T{},
capacity: usize = 0,
// will become
items: Slice,
capacity: usize,
// original
pub const empty: Self = .{};
// will become:
pub const empty: Self = .{
.items = &.{},
.capacity = 0,
};
somewhat contrived, as arraylists .empty is already a non default instantiation. But you get the point.
This is also a good example of why declarations are prefered over default values. items is a slice to the used memory, and capacity is the real size of the memory.
with default values it is far too easy to forget to set one, and create an invalid state that could do anything from crash to overwriting other data you are using.
There is not an init method, init on the DebugAllocator that we were originally talking about is a declaration much like empty
ArrayList has initCapacity which allocates the specified amount of memory, and initBuffer which uses an existing buffer. Neither of which are equivalent to empty.
Understood.
In general, if there’s a struct with some fields, will initializing them with default values in .empty be preferred over doing it in the init method ?
Edit:
Trying to answer my question, assuming there’s a struct call Foo with fields items and capacity, will .empty and init look like the below:
If your function is just returning a constant, why is it a function? There are valid reasons, but they are few and far between
empty here is not a default instantiation, which refers to using the default values of fields, which is done by not explicitly setting those values, see the example I gave earlier. Specifically, the parts under the //original comments
There’s no call-like syntax for i32 because that type isn’t parameterized. But GeneralPurposeAllocator is a function that is called at compile time and returns a struct type – this is how Zig does generics.
In other languages with generics, the generic parameters use special syntax, e.g. Foo<generic parameters> in C++, Foo[generic parameters] in Scala, Foo!(generic parameters) in D … whereas in Zig regular call syntax is used, and comptime and non-comptime parameters can be intermixed. Zig is also different in that comptime functions can return types, whereas in other languages comptime functions are considered to be “templates” that generate different code depending on the generic parameters. (The end result is the same–Zig also generates multiple versions of the code–but the syntax and semantics are different.) Zig’s approach is more unified, but it can be a little confusing to keep track of what is comptime and what is not.
As the doc says: “These are general rules of thumb; if it makes sense to do something different, do what makes sense.”
I think that’s a bit too loose. Better would be “For consistency and readability, these guidelines should be followed unless there is good reason not to–if it makes sense to do something different, do what makes sense. For example, if there is an established convention such as ENOENT , follow the established convention.”