'defer' inside deinit function

In my research project, I use several nested structs that are created and deallocated in threads. To achieve maximum robustness, I’ve switched to wrapping the individual deallocations in a defer within the deinit function, see example:

        pub fn deinit(self: *Self) void {
            defer self.fftForward.deinit();
            defer self.fftBackward.deinit();
            defer self.gpa.free(self.signal);
        }

The idea behind it is that if one of the called deinit functions goes wrong, at least all the others should still be called or more memory should be freed.

What do you think, is this a bit overkill? :thinking:

Zig Zen:

Resource allocation may fail; resource deallocation must succeed.

The nested calls to deinit/free should each not be failing in order for this to be an issue in the first place.

3 Likes

I am not sure what this code example gives you besides reordering the actual calls?

I think you call about panics and if a panic happens, I there isn’t any unwinding.

1 Like

deinit’s in this case are not returning errors so they always succeed and thus defer is useless.
But lets consider example where they can fail.

pub fn deinit(self: *Self) !void {
       try self.fftForward.deinit();
       try self.fftBackward.deinit();
       try self.gpa.free(self.signal);
}

Obviously in this example not everything will be deinited. Lets try defer strategy. We will need to handle error somehow so lets use try.

pub fn deinit(self: *Self) !void {
       defer try self.fftForward.deinit();
       defer try self.fftBackward.deinit();
       defer try self.gpa.free(self.signal);
}

This sadly won’t work since you can’t return error from defer (return value was already decided in normal control flow and can’t be changed). Hm okey lets try to ignore error (or log it but it won’t change example in meaningful way).

pub fn deinit(self: *Self) void {
       defer self.fftForward.deinit() catch {};
       defer self.fftBackward.deinit() catch {};
       defer self.gpa.free(self.signal) catch {};
}

Hm since every error is catched there is no need for defer since everything will be executed anyway.

pub fn deinit(self: *Self) void {
       self.fftForward.deinit() catch {};
       self.fftBackward.deinit() catch {};
       self.gpa.free(self.signal) catch {};
}

But maybe deiniting can go wrong in a way which doesn’t return error. For example it can panic. Well in this case defers won’t be executed anyways so code with or without them will work in the same way.

Here is one way I can imagine been somewhat usefull.

pub fn deinit(self: *Self) anytype!void {
       // If you know error type use correct one here
       var err: anyerror!void = {};
       self.fftForward.deinit() catch |e| err = e;
       self.fftBackward.deinit() catch |e| err = e;
       self.gpa.free(self.signal) catch |e| err = e;
       return err;
}

It will execute every deinit but propagate at least one error above. But what can you do with such error? Idk log it, but you can log it in deinit function directly?

4 Likes

Thanks for your explanation. Sometimes you can’t see the forest for the trees. In my case, the defer is completely unnecessary.