Using Comptime and @export to export anon structs functions?

Right now I am trying to export out some test code using export.

pub const RL_AudioStream = Resource(raylib.AudioStream);
pub const RL_AudioStreamTrait = ResourceTrait(RL_AudioStream);
pub const ResourceManagerForAudioStream = ResourceManager(RL_AudioStream, RL_AudioStreamTrait);
pub const ResourceManagerWrapperForAudioStream = ResourceManagerWrapper(RL_AudioStream, RL_AudioStreamTrait);

pub export fn AudioStreamManager_Init_GPA() ?*ResourceManagerWrapperForAudioStream {
    return ResourceManager_Init_GPA(RL_AudioStream, RL_AudioStreamTrait);
}

And this works great. However I have 30 types I need to do this for. So I wanted to loop over some values to do this for me in Zig. So I was hoping comptime and a loop would be possible. Here is the code which does not work.

const Types = .{
    .{ "AudioStream", raylib.AudioStream },
    .{ "Vector3", raylib.Vector3 },
    .{ "Mesh", raylib.Mesh },
};

const CreateGpaType = struct {
    const Self = @This();

    typeName: []const u8,
    resourceType: type,
    trait: type,

    fn call(self: *Self) ?*anyopaque {
        const result = ResourceManager_Init_GPA(self.resourceType, self.trait);
        if (result) |wrapper| {
            return @as(*anyopaque, @ptrCast(wrapper));
        } else {
            return null;
        }
    }
};

comptime {
    for (Types) |typeInfo| {
        const TypeName = typeInfo[0];
        const ResourceType = typeInfo[1];

        const rlType = Resource(ResourceType);
        const rlTypeTrait = ResourceTrait(rlType);

        const createGpa = CreateGpaType{
            .typeName = TypeName,
            .resourceType = rlType,
            .trait = rlTypeTrait,
        };

        const symbol_name = "Manager_Init_GPA_" ++ TypeName;

        @export(@as(*CreateGpaType, @constCast(&createGpa)).call, .{ .name = symbol_name, .linkage = .strong });
    }
}

Obviously you cannot reference the .call I also tried exporting the struct:

@export(createGpa, .{ .name = symbol_name, .linkage = .strong });

But you cannot export structs, unless marked with extern but you cannot export structs with comptime types i.e:

const CreateGpaType = extern struct {
    const Self = @This();

    typeName: [64]u8,
    resourceType: type,
    trait: type,

    fn call(self: *Self) ?*anyopaque {
        const result = ResourceManager_Init_GPA(self.resourceType, self.trait);
        if (result) |wrapper| {
            return @as(*anyopaque, @ptrCast(wrapper));
        } else {
            return null;
        }
    }
};
error: extern structs cannot contain fields of type 'type'
    resourceType: type,
                  ^~~~

If I try exporting the wrapped struct , which will not work inside a for loop:

fn exportedWrapper() callconv(.C) *anyopaque {
    const result = @as(*CreateGpaType, @constCast(&createGpa)).call();
    return result orelse @panic("exportedWrapper: null returned where *anyopaque is expected");
}

comptime {
    @export(exportedWrapper, .{ .name = symbol_name, .linkage = .strong });
}

I can wrap the struct inside a function to be returned, then in C I can call Manager_Init_GPA_AudioStream(), unravel void* into a struct, which I would need to redeclare in C since Zig will not export that information out due to *anyopaque, which is needed since comptime types cannot be exported.

fn exportedWrapper() callconv(.C) *anyopaque {
    const result = @as(*CreateGpaType, @constCast(&createGpa));
    return result;
}

comptime {
    @export(exportedWrapper, .{ .name = symbol_name, .linkage = .strong });
}

But then it goes back, I wanted to use this inside a for loop and functions cannot be defined inside a for loop…

Any thoughts?

I don’t understand what you’re trying to do here. What are the inputs that you looping over and what do you expect the output to be?

Instead of repeating this for AudioStream, Vector3, Mesh like the repetition below:

// Audio Stream
pub const RL_AudioStream = Resource(raylib.AudioStream);
pub const RL_AudioStreamTrait = ResourceTrait(RL_AudioStream);
pub const ResourceManagerForAudioStream = ResourceManager(RL_AudioStream, RL_AudioStreamTrait);
pub const ResourceManagerWrapperForAudioStream = ResourceManagerWrapper(RL_AudioStream, RL_AudioStreamTrait);

pub export fn AudioStreamManager_Init_GPA() ?*ResourceManagerWrapperForAudioStream {
    return ResourceManager_Init_GPA(RL_AudioStream, RL_AudioStreamTrait);
}

// Vector3
pub const RL_Vector3 = Resource(raylib.Vector3);
pub const RL_Vector3Trait = ResourceTrait(RL_Vector3);
pub const ResourceManagerForVector3 = ResourceManager(RL_Vector3, RL_Vector3Trait);
pub const ResourceManagerWrapperForVector3 = ResourceManagerWrapper(RL_Vector3, RL_Vector3Trait);

pub export fn Vector3Manager_Init_GPA() ?*ResourceManagerWrapperForVector3 {
    return ResourceManager_Init_GPA(RL_Vector3, RL_Vector3Trait);
}


// Mesh
pub const RL_Mesh = Resource(raylib.Mesh);
pub const RL_MeshTrait = ResourceTrait(RL_Mesh);
pub const ResourceManagerForMesh = ResourceManager(RL_Mesh, RL_MeshTrait);
pub const ResourceManagerWrapperForMesh = ResourceManagerWrapper(RL_Mesh, RL_MeshTrait);

pub export fn MeshManager_Init_GPA() ?*ResourceManagerWrapperForMesh {
    return ResourceManager_Init_GPA(RL_Mesh, RL_MeshTrait);
}

I wanted to use comptime to generate pub export fn AudioStreamManager_Init_GPA() ?*ResourceManagerWrapperForAudioStream but for each type. It’s a lot more than one function but I wanted to just start here. You can’t define functions inside a for loops. Which is why you saw the struct code, because I can define an anonymous struct with its functions. One of the problem I have if I try to call CreateGpaType.call is it will also then execute ResourceManager_Init_GPA in comptime which is cannot be done because that function is needed to run at runtime since it doing memory allocation i.e:

pub fn ResourceManager_Init_GPA(comptime T: type, comptime Trait: type) ?*ResourceManagerWrapper(T, Trait) {
    var gpa = std.heap.page_allocator.create(gpaType) catch return null;

This is technically valid code:

comptime {
    for (Types) |typeInfo| {
        const TypeName = typeInfo[0];
        const ResourceType = typeInfo[1];

        const rlType = Resource(ResourceType);
        const rlTypeTrait = ResourceTrait(rlType);

        const createGpa = CreateGpaType{
            .typeName = TypeName,
            .resourceType = rlType,
            .trait = rlTypeTrait,
        };

        const symbol_name = "Manager_Init_GPA_" ++ TypeName;

        @export(createGpa, .{ .name = symbol_name, .linkage = .strong });
    }
}

But the resulting C code or C header file will be empty. Since structs do not export, unless marked as extern, which the struct cannot due to using comptime types:

const CreateGpaType = struct {
    const Self = @This();

    typeName: []const u8,
    resourceType: type,
    trait: type,

    fn call(self: *Self) ?*anyopaque {
        const result = ResourceManager_Init_GPA(self.resourceType, self.trait);
        if (result) |wrapper| {
            return @as(*anyopaque, @ptrCast(wrapper));
        } else {
            return null;
        }
    }
};

I also am not trying to export the struct, I am just wanting to automate the creation of a function like this for export.

pub export fn AudioStreamManager_Init_GPA() ?*ResourceManagerWrapperForAudioStream {
    return ResourceManager_Init_GPA(RL_AudioStream, RL_AudioStreamTrait);
}

I see now. Does this work?

comptime {
  for (Types) |typeInfo| {
    const TypeName = typeInfo[0];
    const ResourceType = typeInfo[1];

    const rlType = Resource(ResourceType);
    const rlTypeTrait = ResourceTrait(rlType);

    const Temp = struct{
      pub fn call() callconv(.C) ?*anyopaque {
         const result =  ResourceManager_Init_GPA(rlType,  rlTypeTrait);
         return if (result) |wrapper|
                      @as(*anyopaque, @ptrCast(wrapper))
                   else 
                      null;
           }
        };

        const symbol_name = "Manager_Init_GPA_" ++ TypeName;

        @export(Temp.call, .{ .name = symbol_name, .linkage = .strong });
    }
}

That was it! It exported the functions:

zig_extern void *Manager_Init_GPA_AudioStream(void);
zig_extern void *Manager_Init_GPA_Vector3(void);
zig_extern void *Manager_Init_GPA_Mesh(void);

So I just needed to add callconv(.C) and didn’t know I could use my comptime generated types inside my call function directly which removes the need to comptime types in my struct! Then that means I can still return a struct and it doesn’t need to be void* anymore!

comptime {
    for (Types) |typeInfo| {
        const TypeName = typeInfo[0];
        const ResourceType = typeInfo[1];

        const rlType = Resource(ResourceType);
        const rlTypeTrait = ResourceTrait(rlType);
        const rlTypeWrapper = ResourceManager(ResourceType, rlTypeTrait);

        const Temp = struct {
            fn call() callconv(.C) ?*rlTypeWrapper {
                const result = ResourceManager_Init_GPA(rlType, rlTypeTrait);
                return if (result) |wrapper|
                    @ptrCast(wrapper)
                else
                    null;
            }
        };

        const symbol_name = "Manager_Init_GPA_" ++ TypeName;

        @export(Temp.call, .{ .name = symbol_name, .linkage = .strong });
    }
}
zig_extern struct ResourceManager_28cimport_struct_AudioStream_2chash2_ResourceTrait_28hash2_Resource_28cimport_struct_AudioStream_29_29_29__2494 *Manager_Init_GPA_AudioStream(void);
zig_extern struct ResourceManager_28cimport_struct_Vector3_2chash2_ResourceTrait_28hash2_Resource_28cimport_struct_Vector3_29_29_29__3123 *Manager_Init_GPA_Vector3(void);
zig_extern struct ResourceManager_28cimport_struct_Mesh_2chash2_ResourceTrait_28hash2_Resource_28cimport_struct_Mesh_29_29_29__3361 *Manager_Init_GPA_Mesh(void);
1 Like