#What is this code doing? (GeneralPurposeAllocator)

1 messages · Page 1 of 1 (latest)

vestal flint
#

Hello, It's day 3 of messing with Zig, and I need to make some allocations in my program.
I saw this code online:

   var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer {
        const deinit_status = gpa.deinit();
        //fail test; can't try in defer as defer is executed after we return
        if (deinit_status == .leak) expect(false) catch @panic("TEST FAIL");
    }

And I'm having some question perhaps someone might be able to anwser them.
First question is regarding the first line of code. So Zig has this concept of returning types in function, and looking into the code of std.heap.GeneralPurposeAllocator(), it returns a struct with a bunch of stuff inside.
The code then (I assume) instantiates that type var gpa = std.heap.GeneralPurposeAllocator(.{}){};, so my first question is, is the code doing exactly that, and is any function ran by default, kinda like how in C++ a default constructor is called? Or is the "constructor" the part where we do std.heap.GeneralPurposeAllocator(.{})? It accepts a config as argument, so is it also a way to construct the instance? Pass values to the function that returns the type or setting values in the {} after?

Second question is, Is calling the deinit() a common pattern for types in Zig?

idle oak
#

std.heap.GeneralPurposeAllocator(.{}) will create the struct. Adding {} will initialize it.

vestal flint
#

Because the function returns a type

idle oak
#

it returns a struct

#

like declaring a struct:

const GeneralPurposeAllocator = struct {};
#

you can see the source code to understand more about it

vestal flint
#

Okay so, followup question.
Let's say I now want to use a std.ArrayList. The function that return the type (std.ArrayList) takes in a type that would be the type of the elements so it know how much to allocate and whatever. However, to get a Pointer (or Reference) to the ArrayList, we do:

    var elements = std.ArrayList(Element).init(allocator);
#

So why not here do we need to do something like:

    var elements = std.ArrayList(Element){}.init(allocator);
#

I'm a bit confused because in C++ for example there is a static keyword for functions that we can call without needing to instantiate

#

But what is happening here?

#

In Zig, are functions inside a structure just... functions in the struct namespace? Is that why the @This() is for, to refer to an instance if the function needs to modify something in a instance of the struct it is declared in? Else it's kind of like a static function?

idle oak
#

init here is a static function in a namespace

coarse kindle
# vestal flint So why not here do we need to do something like: ```c var elements = std.Arr...

T{} is how you initialize a struct. If theres fields with default values (const T = struct { a: usize = 0 }) then the initializer will use that. If not, then you have to provide the field yourself on init: T{ .a = 0 }. https://ziglang.org/documentation/master/#struct

GPA happens to have default values for all its fields so you can do GPA{}. ArrayList however cant have a default value for its allocator field, so you pass that in to init() which returns ArrayList{ .allocator = allocator } (among setting other fields)

grand hound
#

if function doesn't take @TypeOf(instance) as the first argument though, then it's a compile error obviously

vestal flint
#

Hmm I see, so .init() returns a Self, which is a reference to a object of the ArrayList type (is this situation), and fills in the fields of that struct type that the Self references?

grand hound
#

references don't exist in zig per-se; either it's a pointer, or it's a value

vestal flint
grand hound
#

we get a copy of the return value of init(), yes

coarse kindle
#

it should be a semantic copy of the return value, but zig/llvm can choose to return via out-pointer or via registers (implementation defined)

grand hound
vestal flint
grand hound
#

ArrayList(T) is a specific type, distinct from all other ArrayList(X)

coarse kindle
vestal flint
#

I see, I think I get it now

#

I have one final question

#

Regarding the GeneralPurposeAllocator

#

It has a function called allocator() that returns the Allocator interface that the language uses

#
        pub fn allocator(self: *Self) Allocator {
            return .{
                .ptr = self,
                .vtable = &.{
                    .alloc = alloc,
                    .resize = resize,
                    .free = free,
                },
            };
        }
#

What I don't understand in this code is the .vtable = &.{ ... part

#

What is that doing? Storing a reference to a rvalue?

#

It seems like it's constructing a rvalue and getting it's address

coarse kindle
#

That part is kindof abusing the fact that immediate/literals which are known at comptime can be stored in a global so (ATM), its semantically similar to

const vtable = { .alloc = alloc, ... };
fn allocator(self) Allocator {
   return .{ .ptr = self, .vtable = &vtable };
}
#

It is confusing as it looks like its taking the address of a temporary that is invalidated after the function returns

vestal flint
#

Yep ahaha, I see, interesting

grand hound
#

yeah, I tend to write that as .vtable = comptime &.{...} to document that fact in my own code

coarse kindle
#

if you do .alloc = runtimeAllocFn then it becomes invalid like youd expect

vestal flint