A pure-Go Lua 5.4 for embedding small scripts in Go programs: rules, policies, handlers and configuration. Scripts are loaded once, frozen, and then called from any goroutine as typed Go functions, under limits on steps, memory and nesting.
- Easy to use. A small, modern Go API: generics for typed calls,
context.Contextfor cancellation, plain Go values in and out. Pure Go with no cgo, so it cross-compiles and builds anywhere Go does. - Performant. A register VM tuned for the hot paths of embedded scripts, measured against C Lua and other Go implementations (see Performance).
import lua "github.com/folbricht/glua"Eval runs a chunk once and converts its result:
n, err := lua.Eval[int64](ctx, `return 6 * 7`) // 42To call into a script more than once, load it into a Runtime and bind its functions to Go
functions:
rt := lua.MustNew()
script, err := rt.Load(ctx, "greet", `
function greet(name)
return "hello, " .. name
end`)
greet, err := script.Func1[string, string]("greet")
s, err := greet(ctx, "world") // "hello, world"Load takes the script's source and a name that error messages use, here the file's:
src, err := os.ReadFile("policy.lua")
rt := lua.MustNew(lua.Limits{Steps: 100_000, AllocBytes: 1 << 20})
script, err := rt.Load(ctx, "policy.lua", string(src))
allow, err := script.Func2[*Request, int64, bool]("allow")
ok, err := allow(ctx, req, 10) // a plain Go function, safe from any goroutine-- policy.lua
function allow(req, limit)
return req.method == "GET" and req.cost <= limit
endLoad runs the script's top level once and freezes what it built, so one Script serves
every goroutine. The fields of *Request are visible to Lua by their snake_case names.
Go functions are registered as globals when the Runtime is made. Typed adapters such as
lua.Fn1 wrap plain Go functions; a lua.GoFunc reads its arguments and sets its results
itself:
clamp := lua.GoFunc(func(c *lua.Call) error {
x, lo, hi := c.Arg[int64](0), c.Arg[int64](1), c.Arg[int64](2)
return c.Return(min(max(x, lo), hi))
})
rt := lua.MustNew(
lua.Output(os.Stdout), // where print writes; the default discards
lua.Global("upper", lua.Fn1(strings.ToUpper)),
lua.Global("clamp", clamp),
)
script, err := rt.Load(ctx, "main.lua", `
print(upper("hello"), clamp(15, 0, 10)) -- HELLO 10
function level(n) return upper("level ") .. clamp(n, 1, 5) end`)
level, err := script.Func1[int64, string]("level") // takes an int64, returns a string
out, err := level(ctx, 7) // "LEVEL 5"A Go error returned by a host function becomes a Lua error, which the script can catch with
pcall, unless the host made it with lua.Terminate to end the call.
Expressions over a Go struct compile into rules, with misspelt names caught when the rule is made:
rule, err := rt.Expr[Order, bool]("gold", `total > 100 and customer.tier == "gold"`)
ok, err := rule.Eval(ctx, &order)Requirements: Go 1.27 or later, a 64-bit platform. No cgo.
Status: feature-complete and tested against the Lua 5.4.9 test suite and against C Lua by
differential fuzzing. Runtimes run the fast interpreter loop, which trusts bytecode the
verifier has checked; Hardened runs the bounds-checked loop instead.
Time to run a suite of 21 small scripts, relative to C Lua 5.4.9 (geometric mean; lower is faster). Details, per-script numbers and method are in the benchmarks.
| Engine | Relative to C Lua |
|---|---|
| C Lua 5.4.9 | 1.00x |
| glua | 1.23x |
| wangshu | 4.28x |
| GopherLua | 5.21x |
| golua | 5.96x |
- Package documentation: the API, with runnable examples.
- luajson: converting between Lua
values and JSON, and a
jsonmodule for scripts. - The language: how glua differs from Lua 5.4, and which libraries it has.
- Sandbox and limits: what a script can and cannot do, the limits and the fixed caps.
- GopherLua and glua side by side: how common tasks look in each.
- Benchmarks: small scripts in C Lua, glua and other Lua implementations for Go, glua's two interpreter loops, and the cost of calls between Go and Lua.
BSD 3-Clause, see LICENSE.