Question about declaration-level metaprogramming: generating aliases from reflected decls

Hi all,

I ran into a limitation while working on a lua.zig wrapper, and I want to check whether I’m understanding Zig’s design correctly, or whether I’m missing an intended approach.

My practical use case

I have a namespace like this:

const APIs = struct {
    pub extern fn lua_createtable(L: *State, narr: c_int, nrec: c_int) callconv(.c) void;
    pub extern fn lua_pushinteger(L: *State, n: c_int) callconv(.c) void;
    // ...
};

What I want for the public API is:

pub const createtable = APIs.lua_createtable;
pub const pushinteger = APIs.lua_pushinteger;
// ...

So that users (remain parts of my code) can simply write:

const lua = @import("lua.zig");
lua.createtable(...);
lua.pushinteger(...);

(I know there is zlua project in github, but it required to build lua itself into shared library, which is not fill my purpose)

This is currently working fine if I write those aliases manually.

What I hoped to do

Since all the source declarations are already present in APIs, I hoped I could:

  1. iterate over @typeInfo(APIs).@"struct".decls
  2. get each declaration name and member
  3. strip the lua_ prefix (or transform luaL_... as needed)
  4. somehow generate: pub const {new_name} = {member};

But as far as I can tell, Zig currently allows reflection over decls, and allows type construction for fields via @Struct, but does not allow constructing new declarations or injecting them into a container/module scope.

So the only current options seem to be:

  • write aliases manually
  • or generate a .zig file with codegen modified it (I have other helpers in lua.zig, so I could just replace parts of file, not whole).

My questions

1. Is this limitation intentional because of Zig’s “No hidden control flow” / anti-macro philosophy?

I’m wondering whether declaration generation is intentionally excluded because introducing new declarations from comptime would be considered too close to hidden language-level metaprogramming.

In other words: is the inability to generate decls from reflected decls a deliberate design boundary, rather than just an unimplemented feature?

2. Is there any roadmap or long-term plan for declaration-level metaprogramming?

I’m not necessarily asking for a full macro system, but something like:

  • declaration reification
  • constructing container decls in @Struct
  • or some supported way to generate alias declarations from reflected declarations
    Has this been discussed as a possible future direction, or is it considered out of scope?

3. In cases like this, is code generation the recommended Zig-style solution?

For this kind of FFI wrapper ergonomics problem, is the intended answer simply:

“yes, use build-time codegen and generate a .zig file”

If so, that’s totally fine — I mainly want to understand whether this is the idiomatic expected approach today. (btw, the usingnamespace is deleted from language, so a separate .zig file can not hide the indirection from user, so the codegen must modify the part of file)

Thanks!

True.

For the same reason, I don’t think there will be any currently.

Edit: I think manually writing it in this particular scenario is optimal, because the actual requirement here is just to generate a specific structure. I think this approach is not large in scale; string-based metaprogramming is too fragile. If the scale is really unacceptably large, or if there is a very complex need for automatic conversion compatible with multiple versions, code generation at build time may be more recommended.

I’m afraid so; generating at build time is considered the recommended solution.

1 Like

Thanks for the reply! It’s very clear for me.

So there it’s final question: as ‘usingnamespace` is removed, how can I “merge” the generated struct into the unique namespace?

Or is it not an optimal or recommend way in zig? User should use const lua = @import(“Lua.zig”).Lua; instead?