What exactly does Allocator.free() do

So im currently implementing the first steps in a play c compiler. Im using process.run() to perform a gcc command on a c file that i want to generate an assembly and preprocessed file for. What im confused about is when i use gpa.free() on result.stderr and result.stdout it works fine but when i try and use it in result.term it causes a comp error. Now i know and understand that term is a Union type but in the Allocator.free() documentation it states that:
" Free an array allocated with alloc . If memory has length 0, free is a no-op. To free a single item, see destroy"
with the function signature as: anytype

pub fn free(self: Allocator, memory: anytype) void

Looking at the source code for it confuses me why cant it delete a type union if the memory in the signature for free is anytype??

    const result = try std.process.run(gpa, io, .{ .argv = gcc_cmd });
    if (result.term.exited != 0) {
        print("failed with: {s}", .{result.stderr});
    }
    defer {
        gpa.free(result.stderr);
        gpa.free(result.stdout);
        // this is what causes a comp error
        // gpa.free(result.term);
    }

This is the error that i get:

error: access of union field 'pointer' while field 'union' is active
    const slice_info = @typeInfo(@TypeOf(memory)).pointer;
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
/usr/local/Cellar/zig/0.16.0/lib/zig/std/builtin.zig:550:18: note: union declared here
pub const Type = union(enum) {
                 ^~~~~
referenced by:

Any help understanding this would be helpful thanj you.

You only need to free memory that’s been allocated by a corresponding alloc call. As the doc comment for std.process.run states, the caller only ‘owns’ the memory for result.stdout and result.stderr. ‘Owns’ in the context of memory means that the memory has been allocated with alloc and the caller is responsible for freeing it again with free. So you’re correctly calling free on result.stdout and result.stderr, but there’s no need to call free on result.term.

The reason that that isn’t necessary (besides the doc comment saying so) is that result.term is a union of a couple of integers and not a pointer to some allocated piece of memory. It is returned by std.process.run by value and then simply lives on the stack, which is automatically freed at the latest when returning from your current function. Note that Zig’s Allocator interface doesn’t require memory allocated by alloc to actually live on the heap, but it usually does, so if you wouldn’t free it you’d get a memory leak as heap memory is not tied to a function scope and must be freed explicitly.

The anytype arg is a bit misleading, because even though it allows the arg to be of any type, the free function still expects to be handed a pointer to a piece of memory it can then actually free. It checks that by accessing the pointer field of the type information of its arg (to then perform some further type checks), which is not going to be the active field if that arg is e.g. a union.

6 Likes

thank you this part made it click:

since unions live on the stack this makes it makes sense for me.
thank you.

3 Likes