Extend Lua with Zig 1: Hello World

I’m intending to write two or three posts in total, of which this is the first. In this post we write “Hello World!” and our first build.zig script. My goal for the next post or two is to depend on and use Ziglua to write an executable that hosts Lua, and then a dynamic library which can be hosted by your system’s Lua interpreter.

One of the fun parts about putting Lua and Zig together is that (on the syntax level) you see a lot of common features. I put forward some ways you can think of Zig’s syntax as a bit like “ahead-of-time Lua”.

I wrote with approximately the same amount of random discursiveness that you’d get from me if we were pairing to do these tasks. So there are random asides and tangents and a bit more chatting than is necessary to accomplish the task.

Any feedback very welcome :slight_smile:

3 Likes

Oh nice !
It seems a lot of minds converge on the usage of lua with zig !

I wtote a thin wrapper for lua 5.5 if you want to have a look (no AI whatsoever).

It’s not the most up to date code, as I’m using it for another (private) project so I update it only when needed.

https://codeberg.org/mathieu_cayeux/lua.zig

I implemented some nice features such as the ability to add public declarations / functions with only one functions (and the wrapper is created at comptime).

So you can do something like :

const std = @import("std");

const lua = @import("lua-zig");

fn functor(value: u32) u32 {
    std.log.debug("functor({})", .{value});

    return value * value;
}

const LUA_PROGRAM =
    \\function multiply(x, y)
    \\    local z = x * y
    \\    z = functor(z)
    \\    return z
    \\end
;

pub fn main(init: std.process.Init) !void {
    var state: lua.Lua = try .init(init.gpa, .{});
    defer state.deinit();
    defer {
        if (state.diag.hasErr()) {
            std.log.err("{}: {s}", .{ state.diag.err.?, state.diag.message });
        }
    }

    try state.setGlobal("functor", functor);

    const reader = std.Io.Reader.fixed(LUA_PROGRAM);
    try state.loadFromReader(reader);
    // The program must be ran once to load symbols
    try state.run();

    const ret = try state.call("multiply", .{ 2, 3 }, u32);

    std.log.debug("functor(2 * 3) = {}", .{ret});
}

I definitely had the same Lua-JIT feeling that you had !

Nice one !

2 Likes