How to check if a pointer is a single item or multi-item pointer to determine whether to call .free or .destroy

Hi! I’m implementing a basic reference-counted pointer wrapper struct, RcPtr, to use in a WASM library. Most of the implementation is straightforward, but I’m stuck on writing the deinit method.

The struct looks like this:

pub fn RcPtr(comptime T: type) type {
    return extern struct {
        len: usize,
        ptr: *T,

        self_ptr: *RcPtr(T),
        root_ptr: *RcPtr(T),

        // Creating new reference = adding clone
        clones: ?*ArrayList(*RcPtr(T)),
    };
}

RcPtr.ptr could be a single item pointer (eg. T = *u8) — or something that can be cast to a multi item pointer (eg. []u8 or [*]u8). Most of the implementation is straightforward, but I’m stuck on deinit. Deallocating the memory for RcPtr’s underlying value requires knowing whether to call allocator.free, if T is a multi-item pointer, or allocator.destroy if it’s a single item pointer.

My code looks like this:

pub fn deinit(comptime T: type, rc_ptr: RcPtr(T), allocator: Allocator) void {
    var clones = rc_ptr.root_ptr.*.clones.?.*;

    const ref_count = clones.items.len;
    std.log.debug("Refcount {d}", .{ref_count});
    if (ref_count > 0) {
        allocator.destroy(rc_ptr.self_ptr);
        return;
    }

    // No living refs, initialize self destruct sequence!
    const value_ptr = rc_ptr.ptr;

    if (isMultiItemPointer(value_ptr)) {
        // Multi item pointer
        allocator.free(value_ptr.*);
    } else {
        // Single item pointer
        allocator.destroy(value_ptr);
    }

    // This will destroy rc_ptr, since it's in clones, so it's invalid from here on out
    while (clones.popOrNull()) |c| {
        allocator.destroy(c);
    }
    clones.deinit();
}

How can I implement isMultiItemPointer(value_ptr)?

Assuming by your example that the type is known at comptime, you can switch based on @typeInfo.

pub fn deinit(comptime T: type, rc_ptr: RcPtr(T), allocator: std.mem.Allocator) void {
    // do whatever other logic...

    switch (@typeInfo(T).Pointer.size) {
        .One => allocator.destroy(value_ptr),
        .Many => allocator.free(value_ptr),
        else => unreachable, // .Slice, .C
    }
}
2 Likes