1

Given the following C code:

// h.h

typedef enum E {
  A,
  B,
  C
} E;

Zig translates this to

pub const A: c_int = 0;
pub const B: c_int = 1;
pub const C: c_int = 2;
pub const enum_E = c_uint;
pub const E = enum_E;

This is a correct translation, because enums in C aren't namespaced, however I prefer enums to be namespaced for several reasons which I won't get into here. Is there a way to get Zig to generate

pub const E = enum {
  A,
  B,
  C,
};

instead?

1
  • 2
    Zig doesn't have any methods to modify translate-c output. To improve it, either you have to write manual bindings or wrap the translate-c results with your own enums. Commented Dec 27, 2024 at 7:14

1 Answer 1

4

Not with translate-c and that's largely because the translate-c feature of the zig compiler is first and foremost responsible for use via @cImport in zig files.

But you can with comptime reflection of the @cImport.

Typically if I'm building a wrapper around a C lib and I want to warp it with a zig version that defines off the C version.

const c = @cImport({ @cInclude("foo.h"); });

pub const Foo = struct {
    pub const Flags = enum(u16) {
        A = c.FOO_OPTS_A,
        B = c.FOO_OPTS_B,
        //...
    };
};

Although you could go step further and build this with comptime since you can build struct, union or enum, types that have no decls.

Given this header file:

enum Foo {
  FOO_OPTS_A = 1,
  FOO_OPTS_B = 2,
  FOO_OPTS_C = 3,
  FOO_OPTS_D = 4,
};

This zig code will compile it into an enum that's usable like if you hand rolled it.

const std = @import("std");
const c = @cImport({
    @cInclude("enum.h");
});

fn buildEnumFromC(comptime import: anytype, comptime prefix: []const u8) type {
    comptime var enum_fields: [1024]std.builtin.Type.EnumField = undefined;
    comptime var count = 0;

    inline for (std.meta.declarations(import)) |decl| {
        if (decl.name.len < prefix.len + 1) {
            continue;
        }

        @setEvalBranchQuota(10000);
        if (std.mem.eql(u8, decl.name[0..prefix.len], prefix)) {
            enum_fields[count] = .{
                .name = decl.name[prefix.len + 1 ..],
                .value = @field(import, decl.name),
            };
            count += 1;
        }
    }

    return @Type(.{ .@"enum" = .{
        .tag_type = u16,
        .fields = enum_fields[0..count],
        .decls = &.{},
        .is_exhaustive = true,
    } });
}

const FooOptions = buildEnumFromC(c, "FOO_OPTS");

pub fn main() void {
    const flags: FooOptions = .A;

    switch (flags) {
        .A => std.debug.print("Got A\n", .{}),
        .B => std.debug.print("Got B\n", .{}),
        .C => std.debug.print("Got C\n", .{}),
        .D => std.debug.print("Got D\n", .{}),
    }
}

The best part is, because we can declare it as exhaustive ourselves, if there is any change to the C header file, adding or removing values will cause the compiler to error out on the switch in main.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.