Nested extern structs

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:

const Socket = extern struct {
  handle: *anyopaque,
};
const PollItem = extern struct {
  socket: ?Socket,
  // more fields
};

This snippet does not work, I can define PollItem as

const PollItem = extern struct {
  socket: ?*anyopaque,
  // more fields
};

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

socket ?*Socket should work

2 Likes

It doesn’t, zig complains like this

error: extern structs cannot contain fields of type ‘?Socket’

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.

Can you explain what that would look like?
I am not 100% sure if I imagine the sort of code you actually mean.

?* not ?

Could you do something similar to the std.builtin.Type.StructField.defaultValue method?

basically when i have a PollItem and want to use functions defined in Socket, I have to do something like

const socket = Socket{.handle = poll_item.socket.?};

I can’t use the socket inside poll_item directly

This breaks the semantic though, since socket in PollItem expects the handler pointer not a pointer to a handler point.

What about doing something like this?:

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.

But that circles back to my original question, I know this works, but it creates an extra temporary and I want to avoid that.

I redesigned the code and have this as the following

const Socket = opaque {
    // some functions
};

const PollItem = extern struct {
    socket: ?*Socket
};
2 Likes