Correct translation of '?*anyopaque'

I started with the result from translate-c for a C file which has the ?*anyopaque pointer. Then I came across this Wazero page with their example of the malloc and free exports.

So then I went to compare the versions and thought I’ll mimic the Wazero example and change the ?*anyopaque to [*]u8 because the places they appear seem the same.

Are the two equivalent? and safe to use interchangeably in this situation?

Here is the excerpt of the translate-c where I tried to clean-up :

fn CanonicalAbiRealloc(
    arg_ptr: ?*anyopaque,
    arg_oldsz: usize,
    arg_align: usize,
    arg_newsz: usize,
) callconv(.C) ?*anyopaque {
    // zero means to _free_ in ziglang
    // TODO (need to confirm behavior from wit-bindgen version)
    if (arg_newsz == @intCast(usize, 0)) {
        return @intToPtr(?*anyopaque, arg_align);
    }

    // null means to _allocate_
    if (arg_ptr == null) {
        var newslice = gpa.alloc(u8, arg_newsz) catch {
            @panic("FAIL alloc OutOfMem");
        };
        return newslice.ptr;
    }

    var slice = @ptrCast([*]u8, arg_ptr.?)[0..arg_oldsz];
    var reslice = gpa.realloc(slice, arg_newsz) catch {
        @panic("FAIL realloc OutOfMem");
    };
    return reslice.ptr;
}
fn CanonicalAbiFree(
    arg_ptr: ?*anyopaque,
    arg_size: usize,
    arg_align: usize,
) callconv(.C) void {
    _ = arg_align;
    if (arg_size == @intCast(usize, 0)) return;
    if (arg_ptr == null) return;

    const slice = @ptrCast([*]u8, arg_ptr.?)[0..arg_size];
    gpa.free(slice);
}

Yes, the cast seems ok to me in this context, just two things:

[*]u8 cannot be null, and in fact by using .? on arg_ptr you are asserting that the value cannot be null. If the value can indeed be null, you need to properly unwrap it instead of asserting that it’s never going to be null, while if you are confident that arg_ptr is never going to be null, you can change the pointer to *anyopaque and get rid of the ? directly in the function signature (the new signature will still conform to the same C ABI interface).

The original allocation had an alignment which I assume is specified in arg_align. In Zig normally the alignment would be part of the pointer type, but here we’re type erasing it so that knowledge flows into a different argument. Allocators in Zig expect alignment information to be available at comptime so you can’t just reincorporate that knowlegde into a pointer type (@ptrCast([*] align(arg_align) u8, ...) since it would mean creating a new type at runtime. A byte array will have a natural alignment of 1, while other types might have higher alignment requirements, so this cast could in theory be problematic. One solution is to overalign your allocations, which will ensure you will never get alignment problems.

A basic solution would look like this: @ptrCast([*] align(@alignOf(usize)) u8, ...)
Ideally one could get fancier and have a switch that branches into different code paths that have more granular alignment support, but this is already a good starting point (and in fact that’s what C allocators do normally: they give you word-aligned allocations by default).

1 Like

Thank you, very helpful explanation. I’m a big fan of the explicit null rules because I’ve received too many “object reference” exceptions in real life. So I’ll go back over it; I don’t think I can assume it’s safe.