Partial workaround for missing `decls` param to `@Struct`

I just had plain c.sdl* functions and I decided that I want to strip them to sdl.*

Gnerally it is just about trying to find a way to create decls for comptime generated struct (As a probably a lot of people tried: example1, example2). And I found one way!

In zig you can do following:

// It is definitely not ofraid of runtime light...
const NotVampire = struct {
    pub fn test_fn() void {}
    // Just formally it could be runtime. We see it
    // but now it is comptime only type because of this field 
    i_am_variable_i_swear: @TypeOf(test_fn) = test_fn,
    variable: u8,
};

But you when try check the @typeInfo(NotVampire).@"struct".field_names you will see the i_am_variable_i_swear and will be able to check it is type and etc. So I understood that it can be abused used for my purposes

It creates kind of “illegal” and very restrained structs but it CREATES NEW STRUCTS WITH FUNCTIONS!. Because I am so excited it seems to me someone already did this before me (maybe even in the linked threads, I am not sure)…

Also if you are not following rules (won’t mention them) you are going to either scare the Maker process with horrible code or outright kill it and get SIGSEGV (this part probably should be reported as bug :thinking:)

const std = @import("std");

const Amogus = struct {
    fn amogus_speech() void {
        std.debug.print("Amogus speech: I am normal crewmate. Don't kill me\n", .{});
    }
};
const Normal = struct {
    pub fn test_fn() void {}
    i_am_variable_i_swear: @TypeOf(test_fn) = test_fn,
    arg: u8,
};
pub fn main(init: std.process.Init) !void {
    // To show that main wasn't just wholly optimized to comptime
    var buf: [1024]u8 = undefined;
    var stdin_reader = std.Io.File.stdin().reader(init.io, &buf);
    const stdin = &stdin_reader.interface;
    const input = try stdin.takeByte();

    // const amogus: Amogus = .{};
    // amogus.amogus_speech(); // Illegal: amogus_speech is not a field or member function

    // Amogus.amogus_speech(); // Legal

    // Impostor().amogus_speech(); // Illegal: amogus_speech is actualy a field (which exist only for instances not for type itself)

    // const impostor: Impostor() = .{ .variable = input }; // Illegal: comptime-only type can't store runtime value

    const impostor: Impostor() = .{ .variable = 32 };
    impostor.amogus_speech();
    impostor.impostor_speech(input);
    std.debug.print("{}\n", .{impostor.variable});

    // Illegal, when you add any non `.@"comptime" = true` fields but you still can instanciate it
    std.debug.print("Size of impostor: {}\n", .{@sizeOf(Impostor())});
}

fn Impostor() type {
    const anon = struct {
        fn impostor_speech(runtime: u8) void {
            std.debug.print("It's not SIGSEGV. I am born! Muahahahahahaha!\n", .{});
            std.debug.print("I had also ripped off a part of amogus: `amogus_speech()`\n", .{});
            std.debug.print("I know runtime values: {c}\n", .{runtime});
            std.debug.print("I have variables, but don't know how to access them\n", .{});
            std.debug.print("And I don't know my runtime size\n", .{});
            std.debug.print("I exist but I am crippled (ㅠ﹏ㅠ)\n", .{});
        }
    };
    const info = @typeInfo(struct {}).@"struct";
    const new_names = [_][]const u8{ "amogus_speech", "impostor_speech", "variable" };
    const new_types = [_]type{ @TypeOf(Amogus.amogus_speech), @TypeOf(anon.impostor_speech), u32 };
    const new_attrs = [_]std.lang.Type.Struct.FieldAttributes{
        .{ .@"comptime" = false, .default_value_ptr = Amogus.amogus_speech },
        .{ .@"comptime" = false, .default_value_ptr = anon.impostor_speech },
        .{ .@"comptime" = false },
    };
    return @Struct(info.layout, info.backing_integer, &new_names, @ptrCast(&new_types), @ptrCast(&new_attrs));
}

P.S It is more like “showcase” but I am not sure. It seems to be more suitable here… I found a way to not only shoot my legs but also legs of compiler :laughing:. And yeah, please tell me if it was really already known

2 Likes

This does seem like it’s going a little too hard when a simpler solution may be available, what problem are you trying to solve? This appears like a question of how to auto-generate bindings, where it may be more sensible to generate zig as part of the build process than use comptime like this.

Can you show a concrete example of the c.sdl* functions you want to be able to use as sdl.*?

2 Likes

Yeah, I know thank you. I just wanted to shoot my legs. It is not like I can write even 10 lines of normal code, I am probably even more crippled than my code so if something is said is unpractical/hacky/impossible, I just have fun with it and shoot my legs and turns out legs of compiler too…

Happy to help with some fun!

But at least for me this needs something more concrete - what are you trying to do? I ask because without definition it seems like this is all you need:

const sdl = struct {
    const foo = c.sdlFoo;
};

But given the complexity of exploration you seem to want more fun than that. Are you trying to auto-generate the sdl namespace from what’s in c? If so, what is in your c? What are your constraints?

Exactly. But I want to do it in comptime without code generators :grin:

Yeah, I want to strip whole c. Translate all functions and all variables/structs and etc to new names without prefix. I generally already mostly (very sparsely but I need mostly to figure out one “bug” and bring everything together) did for superficial structs of my own, it is now time to just test it in a wild…

Well, to be preciseI don’t even need sdl anymore, I trashed it out and will just render to memory and save as file… But you know vulkan seems not so easy target as sdl so I decided to start from sdl… So yeah I have only vulkan now in c and nothing else

It is perfect because c namespace is going to be comptime so no problems with no runtime support :slight_smile:

1 Like

Turned out there is one more impossible thing. This time it is probably impossible for real: tackling @compileError

If you have:

const c = struct {
    pub const __seg_gs = @compileError("unable to translate macro: undefined identifier `address_space`");
}

It is ok till you do @field(c, "__seg_gs") which makes it impossible to even safely iterate decls if there is at least one @compileError

Fun has ended :cry:

fn vkStrip(comptime space: type) type {
    @setEvalBranchQuota(std.math.maxInt(u32));
    const info = switch (@typeInfo(space)) {
        .@"struct" => |@"type"| @"type",
        else => @compileError("'namespace' must be a struct"),
    };

    var new_names: [info.decl_names.len + info.field_names.len][]const u8 = undefined;
    var new_types: [info.decl_names.len + info.field_names.len]type = undefined;
    var new_attrs: [info.decl_names.len + info.field_names.len]std.lang.Type.Struct.FieldAttributes = undefined;

    for (info.field_names, 0..) |name, i| {
        new_names[i] = name;
        new_types[i] = info.field_types[i];
        new_attrs[i] = info.field_attrs[i];
    }

    for (info.decl_names, info.field_names.len..) |name, i| {
        new_names[i] = name;
        new_attrs[i] = .{};
        new_types[i] = @TypeOf(@field(space, name));
    }

    return @Struct(info.layout, info.backing_integer, &new_names, &new_types, &new_attrs);
}
1 Like

As I expected someone already did it (and even has shown on ziggit) Converting a C API to Zig with the help of comptime

1 Like

If what you want to do is automatically generate renaming functions through code, rather than writing them by hand, then you can absolutely write a Zig script that runs at compile time and produces a Zig module—just like this one.

For almost any process that can be automated, this is essentially the best solution; there’s no need to dive into complicated comptime gymnastics. Personally, I consider this approach to be Zig’s equivalent of macros, because it operates at the text or AST level, much like macros in other languages, and Zig’s reflection capabilities are still sufficient to obtain some more detailed information.

Yeah good point but I wanted to experiment with this approach and for vulkan I would rather write custom python generator based on official vulkan-object and vk.xml (what I am going to do one day) than writing zig file from translate-c file. vk.xml->zig rather than vk.xml->c->translate-c->zig. While comptime generation is just a fun experiment

You may have misunderstood me. The case of vk.xml → Zig is the same—this simply means you cannot reuse Zig itself as a parser for the input file, but that doesn’t prevent it from serving as a script that generates file content at compile time. You can take any type of file as input, not just Zig or C; as long as you implement the corresponding parsing tools, files like XML, JSON, or even C++ and Rust can all be used as generation templates. Moreover, this approach is more efficient than pure comptime code.

Ok. It convinced me. The code looks much less scary and long than I thought. Thanks for convincing and it really works fast. Much faster than I thought it would be…