I’m creating a binding to ZeroMQ so I have to dealing with lots of *anyopaque and extern struct, but I have a problem with Zig not allowing nested extern struct. My situation is:
but that requires me to create a temporary Socket variable every time I need to work with the socket inside PollItem (not a big cost, I know), I want to know if there’s a way to circumvent creating a temporary variable here
It’s a problem with the optional type, not with nesting structs.
Compiling the code snippet I get
error: extern structs cannot contain fields of type '?Socket'
note: only pointer-like optionals are extern compatible
C doesn’t have a concept that maps 1:1 to Zig’s optional types (?Socket, “Socket or typesafe null value”), so the only way to include optional types in C structs is to make them optional pointers to structs (?*Socket, “pointer to Socket or null pointer”), which does exist in C.
const std = @import("std");
const Socket = extern struct {
handle: *anyopaque,
fn print(self: Socket) void {
std.debug.print("handle: {}\n", .{self.handle});
}
};
const PollItem = extern struct {
socket: ?*anyopaque,
// more fields
pub fn init(socket: Socket) PollItem {
return .{
.socket = socket.handle, // put the raw `handle` ptr in socket
};
}
pub inline fn getSocket(self: *PollItem) ?Socket {
return .{ .handle = self.socket orelse return null }; // turn it back into the handle
}
};
pub fn main() !void {
const any: *anyopaque = @ptrFromInt(0xF0F0F0F0F0F0);
const socket: Socket = .{ .handle = any };
var item: PollItem = .init(socket);
item.getSocket().?.print();
}
Because Socket is just a handle that contains a single pointer there is no reason to use *Socket for methods that need to mutate, because all the mutation happens through self.handle anyway. That means we don’t have a problem with the Socket being a constant temporary value returned by getSocket.