#How do you setup an arena that belongs to a parent struct?

1 messages · Page 1 of 1 (latest)

polar hound
#

I have a struct that uses an arena, because everything it creates needs to be destroyed at the same time, and this makes the destory step much much faster:

/// Create a `Resources` object that holds all its
/// objects in its own arena.
pub fn create(gpa: Allocator) error{OutOfMemory}!*Resources {
    var arena = try gpa.create(std.heap.ArenaAllocator);
    errdefer gpa.destroy(arena);
    arena.* = std.heap.ArenaAllocator.init(gpa);
    const arena_allocator = arena.allocator();
    const resources = try gpa.create(Resources);
    resources.* = Resources{
            .arena = arena,
            .arena_allocator = arena_allocator,
            .folder = "",
            .used_resources = null,
    };
    return resources;
}

Im not sure if this is how I should be setting up an arena. This was one of the very first times I tried to use an arena, so its probably odd 🙂

spare prawn
#

Why are you storing the arena_allocator and parent_allocator in the struct?

polar hound
spare prawn
#
pub fn init(gpa: Allocator) Resources {
  return .{
    .arena = .init(gpa),
    .folder = "",
    .used_resources = null,
  };
}

is how that should look

#

create in general is bad habit in zig or returning allocated structs

polar hound
#

Oh, I only do it because thats how I saw people doing it when I was learning.

spare prawn
#

You rarely actually need it. If the caller wants the thing on heap, they can do:

const res = try allocator.create(Resources);
res.* = .init(gpa);
polar hound
#

I've not used that pattern before (not exactly like taht anyway)

spare prawn
#

Similarily, if you found yourself in trap that you need to allocate in your constructor because you are referencing the struct itself, instead do inplace init:

var thing: Thing = undefined;
thing.init(); // it's okay for init to now reference itself
polar hound
#

I guess the problem is, this struct is like a mega struct (I trimmed some stuff out). There other things need to be initialised. and they use the old style of initialisation, i.e.

pub fn create(gpa: Allocator) error{OutOfMemory}!*Resources {
     var arena = try gpa.create(std.heap.ArenaAllocator);
     errdefer gpa.destroy(arena);
     arena.* = std.heap.ArenaAllocator.init(gpa);

     const arena_allocator = arena.allocator();
     const normalise = try Normalize.init(arena_allocator);
     const resources = try gpa.create(Resources);
    ....
edgy cape
#

I fail to see how that is a limitation, or what you even mean by "old style of initialisation"

spare prawn
polar hound
#

oh, that stuff can be in the init instead of the create. (This code doesnt even have an init, it really was one of the first things I coded in zig)

spare prawn
#

You are using create to workaround the issue that you do not have pointer stability

polar hound
#

Thanks for the pointers. I think I know what to do to clean it all up.

polar hound
#

Thanks, I love the tigerbeetle stuff, I'll give it a read.

#

Ok, I found out why I hade the arena initialised first, how do I fix this problem? I need the arena inside the creation of the struct:

polar hound
#

Oh, I havent heard that term before

#

It takes 170ms, I want to do it on startup rather than when it is first used if thats what you mean.

#

(Thats the unicode normalisatin library, its very slow to start up)

spare prawn
#
pub fn init(self: *Resources, gpa: Allocator) !void {
  self.* = .{
    .arena = .init(gpa),
    .normalize = undefined,
    ...
  };
  self.normalize = try .init(self.arena.allocator());
}
edgy cape
#

does Normalise store the allocator, if so than inplace init, thats this earlier example #1484149743063007323 message
if it does not store it, you can just first create the arena and normalise as variables before the larger struct

polar hound
#

Yea it stores it....

#

So in your version, its ok that the areana is copied because normalise only has the interface?

spare prawn
#

^ updated it because I wrote it wrong lol

polar hound
#

Oh, I like that better!

spare prawn
#

you can chain inplace inits so if normalize also is complicated struct like that, you might just want self.normalize.init(...) too

polar hound
#

Thanks. I apprecaite the tips. Half the time im just by myself just randomly trying things to see what works best. This is a lot better than before.

edgy cape
spare prawn
spare prawn
#

Those pesky undefines are the price you pay, but in the end you get better code. At least until we have @resultLocation() I wouldn't rely on RLS getting it right zeroClueless

edgy cape
#

yeah, i wouldnt recomend it either, i just thought it was interesting

#

wouldn't @resultLocation refer to the rls of the function return, not the pointer parameter being written to,
though it would remove the need for the inplace init patern

polar hound
#

Ignoreing the fact that a lot of things have changed from when I started, When I was learning, I didnt have "patterns" to imitate, so a lot of this was just random hacked toegether until it worked.

I think I gpa.create'ed the base arena object because it fixed the bug where I was copying the arena. I never needed to gpa.create it in the first place.

spare prawn
polar hound
#

I presume once zig becomes more stable everyone will have clear patterns in tutorials and what not.

spare prawn
#

and zig doesn't have language level concept of pinned structs and such

#

so I can see why you thought that is the answer

polar hound
#

Yep. I only understand it now. 🙂

spare prawn
#

Rule of thumb is that if you need to allocate something that doesn't actually have to grow (or shrink), there's probably some other way

#

gpa.create for instance structs like this is like creating object in python/js :P

#

(though even worse)

polar hound
#

Yea, thats the other thing I wasnt really appreciate when I first learnt zig (and even rust).

I knew heap and stack, but I never had a mental model of deciding where things should live.

Learning a lot from zig.

spare prawn
#

one other case where actually creating struct on heap is useful when you want a opaque type, for example doing a stable C API, or you want to hide the implementation / size of the struct

polar hound
#

Yea. I think when I used to use godot and tried to make zig plugins, that was my problem I was having. Not really having a mental model for what needed to live on past the lifetime of the function.

#

Anyway, Im happy that this is now making much more sense.

spare prawn
#

For example, lets have a C API that exposes foo_create() and foo_do_thing()
This first version does not use a opaque handle:

var foo: Foo = undefined;
foo_create(&foo);
foo_do_thing(&foo);

This means the ABI boundary depends on the size of the Foo struct, so if library is updated and the foo struct is changed in incompatible way (fields removed, no version field and size grows), the program probably will crash or such.

Then if we use opaque handle:

var foo: *Foo =  foo_create();
foo_do_thing(foo);

The ABI no longer depends on the size of the Foo, it is backwards compatible, and can be forwards compatible with version field.

edgy cape
#

I mean, you could get an opaque type on the stack like

struct {
   bytes: [@sizeOf(PrivateType)]u8 align(@alignOf(PrivateType),
   fn foo(o: *@This()) void {
       const priv: *PrivateType = @ptrCast(o);
       ...
   }
}

ofc, then the user does depend on the size and alignment of the type. but not the fields.

polar hound
#

Oh, ok yep. That makese sense

spare prawn