Do I need to unwrap `?*anyopaque` before using `@ptrCast`?

Do I need to unwrap optionals before @ptrCast?

fn unwrapsOptional(arg: ?*anyopaque) callconv(.c) void {
    const event: *std.Thread.ResetEvent = @alignCast(@ptrCast(arg.?));
}

fn noUnwrapOptional(arg: ?*anyopaque) callconv(.c) void {
    const event: *std.Thread.ResetEvent = @alignCast(@ptrCast(arg));
}

If the result is a non-optional pointer, like here, then you should unwrap. Otherwise, you’ll be allowing address 0 for the result pointer.

Sidenote, it seems pointer casting an optional to a non-optional pointer could be made a compile error.

3 Likes

@ptrCast

Optional Pointers are allowed. Casting an optional pointer which is null to a non-optional pointer invokes safety-checked Illegal Behavior.

@ptrCast(arg.?) and @ptrCast(arg) are functionally equivalent, unless the destination pointer type is also optional or allowzero, in which case the explicitly unwrapping one will invoke safety-checked UB.

9 Likes

This seems like poor advice and a strange proposal. As @castholm correctly states above, the explicit .? unwrap makes zero difference to the behavior here. Address 0 is not valid for a non-optional pointer, so rather than “allowing address 0 for the result pointer” it of course triggers Safety-Checked Illegal Behavior to perform such a cast – just like .? would.

Either way, to be clear, you should only perform this pointer cast if you know for a fact that arg is not null, and is in fact a valid pointer to a ResetEvent – safety checks are not a substitute for error handling! In other words, if the application should panic in the null case, don’t rely on the safety check: do it explicitly with e.g. arg orelse @panic("null").

Regarding your idea that it should be a compile error, I don’t see any reason for that. @ptrCast is a fundamentally dangerous builtin: in accepted (but not yet implemented) semantics, it is Illegal Behavior (sometimes with a safety check) to @ptrCast a pointer to an “incorrect” type. @ptrCast means “yes, I know the type system doesn’t know that this pointer is a *T, but I promise it actually is”. Why should asserting a pointer is not address 0 be an exception to this definition?

7 Likes

Yep, it’s clear to me now. Sorry for my morning brain fart. Thank you both, @castholm and @mlugg!

3 Likes

To add a bit of context for future readers, I was providing a callback to a c library that allows the user to pass a pointer (sometimes called userdata) to go along with whatever data the callback is returning.

The type of such a “userdata” pointer is typically ?*anyopaque.

The key thing to understand here is that zig uses the zero address as null for “normal” optional pointers. So there is no size / alignment difference between an optional pointer and a non-optional pointers, which makes the @ptrCast valid whether the item is unwrapped or not.

4 Likes