Calling C API with pointer inside of a struct

Consider the following C API:

struct Options {
    uint32_t a;
    uint32_t b;
    void* extra;
};

struct OptionsExtra {
    uint32_t c;
    uint32_t d;
};

void foo(struct Options const* options);

It can be used from C as follows:

foo(&(struct Options) {
    .a = 1,
    .b = 2,
    .extra = &(struct OptionsExtra) {
        .c = 3,
        .d = 4,
    }
});

I redeclared this API in Julia:

struct Options
    a::UInt32
    b::UInt32
    extra::Ptr{Cvoid}
end

struct OptionsExtra
    c::UInt32
    d::UInt32
end

function foo(options)::Cvoid
    ccall((:foo, lib), Cvoid, (Ref{Options},), options)
end

How should I construct extra pointer in a call to foo?

extra = OptionsExtra(3, 4)
foo(Options(1, 2, #= what goes here? =#))

pointer function only works with arrays and pointer_from_objref only with mutable objects.

As a workaround I can put extra in an array:

extra = [OptionsExtra(3, 4)]
GC.@preserve extra foo(Options(1, 2, pointer(extra)))

But this seems overcomplicated and suboptimal as it involves heap allocation while C version allocates everything on the stack.

Is there any better way to do this?

You can make OptionsExtra mutable, so you can directly use pointer_from_objref on it without wrapping it in another container. But I’m not sure this solution will satisfy you.

You could also use Ref(OptionsExtra(3,4)) + Base.usafe_convert.

There is no way to avoid heap allocations if your C API expects a pointer, you could change the C API to pass-by-value though.