mmcdole/lunar
A Lua 5.1 runtime for Go. Contribute to mmcdole/lunar development by creating an account on GitHub.
5 stars · 0 forks · 0 watchers
README
Lunar
A fast, memory-efficient Lua 5.1 runtime for Go.
Lunar is a complete implementation of Lua 5.1 (plus 5.2-style goto and labels) in pure Go. The compiler, VM, and standard libraries are all tested against the reference implementation. Quick start package main
import ( "fmt"
"github.com/mmcdole/lunar" )
func main() { state, err := lua.New(lua.Options{Libraries: lua.CoreLibraries()}) if err != nil { panic(err) } defer state.Close()
results, err := state.DoString("@demo.lua", `return ("lunar"):upper()`) if err != nil { panic(err) }
text, _ := results[0].AsString() fmt.Println(text) // LUNAR } Libraries and file access are opt-in. lua.CoreLibraries() installs everything that can't touch the host, and lua.FullLibraries() adds IO, OS, and debug for scripts you trust. Loading scripts from disk is disabled until you configure a ScriptLoader. Calling Go from Lua NewNativeFunction wraps a Go function as a Lua value. Inside the callback, everything goes through the Frame: read arguments with its typed accessors, then return a result or throw a Lua error: // Callable from Lua as greet(name). greet, err := state.NewNativeFunction(func(frame lua.Frame) lua.Outcome { name, ok := frame.String(0) // first argument, must be a string if !ok { frame.ThrowArgTypeError(0, lua.StringKind) } return frame.ReturnString("hello, " + name) }) if err != nil { panic(err) } if err := state.SetGlobal("greet", greet.Value()); err != nil { panic(err) }
// results[0] is "hello, moon" results, err := state.DoString("@hello.lua", `return greet("moon")`) See Embedding Lunar for the full guide: calls, callbacks, tables, errors, cancellation, coroutines, and lifecycle. Performance Medians from 15 runs on an Apple M3 Pro with Go 1.25.1 at source revision 1d43aec. Lower is better.
Established Lua program Lunar GopherLua go-lua
binary-trees 162.2 ms 172.4 ms 179.8 ms
fannkuch-redux 23.63 ms 33.17 ms 40.14 ms
n-body 59.03 ms 193.39 ms 197.77 ms
spectral-norm 52.82 ms 160.14 ms 153.66 ms
Embedding operation Lunar GopherLua go-lua
Go calls Lua with scalar arguments 61.21 ns 61.51 ns 146.80 ns
Lua calls Go 1,000 times 62.49 µs 99.58 µs 84.37 µs
Lua echoes a 128-byte Go string 86.20 ns 81.41 ns 144.00 ns
Lua checksums a reused Go-built table 312.4 ns 578.3 ns 972.9 ns
Build a table in Go, then checksum it in Lua 2.230 µs 1.405 µs 1.645 µs
Loading a 9 MB CBOR graph Lunar GopherLua
Total memory allocated while loading 107.5 MB 784.6 MB
Live heap added after garbage collection 72.24 MiB 542.26 MiB
The full results include confidence intervals, allocation counts, and raw output; the benchmark protocol lists the commands, inputs, and runtime versions. Compatibility
Lunar GopherLua Shopify go-lua
Lua version Lua 5.1 with Lua 5.2-style goto Lua 5.1 with Lua 5.2-style goto Lua 5.2
Go API Functions return typed values; callbacks use typed Frame accessors Values are LValue objects; callbacks pass arguments and results through an LState stack Mirrors the Lua C API; values are addressed by numeric stack position
Libraries in a new state None by default; select any subset at construction or open one later All standard libraries None; call OpenLibraries or open them individually
Script-file loading Denied by default; select host files, an fs.FS, or a host function Ambient OS file access Ambient OS file access when the applicable libraries are open
Coroutines Supported from Lua and Go Supported from Lua and Go Not implemented
Cancellation One installed context covers execution, loading, and coroutines One context on the state, execution only No context-based cancellation
os.exit Returns an *lua.ExitRequest to Go Exits the entire Go process Exits the entire Go process
Binary chunks Reads and writes Lua 5.1 bytecode when byte order and type sizes match Cannot read or write standard Lua bytecode files Reads and writes Lua 5.2 bytecode through the Go API
Scope The compiler, VM, standard libraries, coroutines, binary chunks, weak tables, and finalizers are implemented. Current intentional limits:
no C ABI, native C-module loading, or light userdata; no debug.sethook or debug.gethook; garbage collection runs synchronously rather than incrementally; no deterministic VM-instruction budget; and no table-iteration helpers beyond the Table.Next primitive.
A State serves one goroutine at a time; separate States can run concurrently. The public embedding API is still stabilizing. Documentation
Embedding: setup, calls, callbacks, values, contexts, errors, and lifecycle Architecture: compiler, VM, runtime representation, and API boundaries Language compatibility: the goto extension and intentional Lua 5.1/5.2/LuaJIT choices Collection: Lua reachability, weak tables, and finalization Third-party notices: adapted algorithms, artwork, and benchmark sources
Lunar is available under the MIT License.
