Pass self by value or pointer to deinit()

I’m wondering if there’s any guidance on when to pass self to the deinit() method of a struct by value, and when to pass a pointer. I’ve seen examples in the standard library (for example array_list.zig) which do both - and in the latter case they also assign self.* to undefined. For example, ArrayListAligned has:

pub fn deinit(self: Self) void {
    if (@sizeOf(T) > 0) {;

but ArrayListAlignedUnmanaged has:

pub fn deinit(self: *Self, allocator: *Allocator) void {;
    self.* = undefined;

Any advice on when to use which of these patterns? I am aware that passing by value means that the struct itself can’t be modified, and that using the second example means that the struct can’t be const - is there any more to it than that? When writing a library, which should I use?


I don´t see a real problem with mixing these.
As you pointed out, you only need a pointer if you plan to write to the object during deinit(). If it is implied, that you cannot use the object at all after deinit(), then it probably doesn’t make a difference, right? But perhaps you can detect use-after-free situations, in some build configurations if you set the memory to undefined.

I voiced my thoughts and concerns about that here:


The question is, is there going to be a copy here?
If I have a large struct? Won’t Zig copy the entire thing to deinit() ?

Passing arguments like this makes them const and lets Zig choose how to pass the argument, so it will still pass a pointer under the hood for big structs.

In general, if you don’t need to modify the struct, always pass it around by value.

This auto-pass-by-value/const reference initially confused me, but as I’ve written more Zig code, it’s actually really nice because I only need to worry about two cases: Do I want this function to modify this thing or not?

On that note, what is the heuristic that Zig uses to choose between passing by value vs by const reference?

1 Like