2025-05-31 17:42:58 -05:00
2025-03-07 07:25:34 -06:00
2025-05-31 17:42:58 -05:00
2025-04-04 21:32:17 -05:00
2025-05-31 17:49:25 -05:00
2025-05-31 17:42:58 -05:00
2025-05-31 17:42:58 -05:00
2025-06-03 17:01:56 -05:00
2025-05-31 17:42:58 -05:00
2025-01-25 22:10:49 -06:00
2025-02-26 07:00:01 -06:00
2025-06-03 17:01:56 -05:00
2025-05-31 17:42:58 -05:00
2025-05-31 17:42:58 -05:00
2025-05-31 17:42:58 -05:00

LuaJIT Go Wrapper

This is a Go wrapper for LuaJIT that makes it easy to embed Lua in your Go applications. We've focused on making it both performant and developer-friendly, with an API that feels natural to use.

What's This For?

This wrapper lets you run Lua code from Go and easily pass data back and forth between the two languages. You might want this if you're:

  • Adding scripting support to your application
  • Building a game engine
  • Creating a configuration system
  • Writing an embedded rules engine
  • Building test automation tools

Get Started

First, grab the package:

go get git.sharkk.net/Sky/LuaJIT-to-Go

Here's the simplest thing you can do:

L := luajit.New() // pass false to not load standard libs
defer L.Close()
defer L.Cleanup()

err := L.DoString(`print("Hey from Lua!")`)

Working with Bytecode

Need even more performance? You can compile your Lua code to bytecode and reuse it:

// Compile once
bytecode, err := L.CompileBytecode(`
	local function calculate(x)
		return x * x + x + 1
	end
	return calculate(10)
`, "calc")

// Execute many times
for i := 0; i < 1000; i++ {
	err := L.LoadAndRunBytecode(bytecode, "calc")
}

// Or do both at once
err := L.CompileAndRun(`return "hello"`, "greeting")
Benchmark                           Ops/sec         Comparison
----------------------------------------------------------------------------
BenchmarkSimpleDoString             2,561,012        Base
BenchmarkSimplePrecompiledBytecode  3,828,841        +49.5% faster
BenchmarkFunctionCallDoString       2,021,098        Base
BenchmarkFunctionCallPrecompiled    3,482,074        +72.3% faster
BenchmarkLoopDoString                 188,119        Base
BenchmarkLoopPrecompiled              211,081        +12.2% faster
BenchmarkTableOperationsDoString       84,086        Base
BenchmarkTableOperationsPrecompiled    93,655        +11.4% faster
BenchmarkComplexScript                 33,133        Base
BenchmarkComplexScriptPrecompiled      41,044        +23.9% faster

Registering Go Functions

Want to call Go code from Lua? It's straightforward:

// This function adds two numbers and returns the result
adder := func(s *luajit.State) int {
	sum := s.ToNumber(1) + s.ToNumber(2)
	s.PushNumber(sum)
	return 1  // we're returning one value
}

L.RegisterGoFunction("add", adder)

Now in Lua:

result = add(40, 2)  -- result = 42

Function Validation

Validate arguments easily:

calculator := func(s *luajit.State) int {
	if err := s.CheckArgs(
		luajit.ArgSpec{Name: "x", Type: "number", Required: true, Check: luajit.CheckNumber},
		luajit.ArgSpec{Name: "y", Type: "number", Required: true, Check: luajit.CheckNumber},
	); err != nil {
		return s.PushError(err.Error())
	}

	result := s.ToNumber(1) + s.ToNumber(2)
	s.PushNumber(result)
	return 1
}

Working with Tables

Lua tables are powerful - they're like a mix of Go's maps and slices. We make it easy to work with them:

// Go → Lua
stuff := map[string]any{
	"name": "Arthur Dent",
	"age": 30,
	"items": []float64{1, 2, 3},
}
L.PushValue(stuff)  // Handles all Go types automatically

// Lua → Go with automatic type detection
L.GetGlobal("some_table")
result, err := L.ToTable(-1)  // Returns optimal Go type (typed array, or map[string]any)

Table Builder

Build tables fluently:

L.NewTableBuilder().
	SetString("name", "John").
	SetNumber("age", 30).
	SetBool("active", true).
	SetArray("scores", []any{95, 87, 92}).
	Build()

Table Field Access

Get fields with defaults:

L.GetGlobal("config")
host := L.GetFieldString(-1, "host", "localhost")
port := L.GetFieldNumber(-1, "port", 8080)
debug := L.GetFieldBool(-1, "debug", false)

Error Handling

We provide useful errors instead of mysterious panics:

if err := L.DoString("this isn't valid Lua!"); err != nil {
	if luaErr, ok := err.(*luajit.LuaError); ok {
		fmt.Printf("Error in %s:%d - %s\n", luaErr.File, luaErr.Line, luaErr.Message)
		fmt.Printf("Stack trace:\n%s\n", luaErr.StackTrace)
	}
}

Memory Management

The wrapper uses bytecode buffer pooling to reduce allocations:

// Bytecode buffers are pooled and reused internally
for i := 0; i < 1000; i++ {
	bytecode, _ := L.CompileBytecode(code, "test")
	// Buffer automatically returned to pool
}

Function pointers are managed safely:

// Functions are registered in a thread-safe registry
L.RegisterGoFunction("myFunc", myGoFunc)
defer L.Cleanup()  // Cleans up all registered functions

Best Practices

State Management

  • Always use defer L.Close() and defer L.Cleanup() to prevent memory leaks
  • Each Lua state should stick to one goroutine
  • For concurrent operations, create multiple states
  • You can share functions between states safely
  • Keep an eye on your stack management - pop as many items as you push

Bytecode Optimization

  • Use bytecode for frequently executed code paths
  • Consider compiling critical Lua code to bytecode at startup
  • For small scripts (< 1024 bytes), direct execution might be faster due to compilation overhead

Type Conversion

  • Use ToTable() for automagic type detection and optimized Go arrays/maps
  • Use PushValue() for automagic Go-to-Lua conversion
  • Leverage typed field accessors for config-style tables

Advanced Features

Bytecode Serialization

You can serialize bytecode for distribution or caching:

// Compile once
bytecode, _ := L.CompileBytecode(complexScript, "module")

// Save to file
ioutil.WriteFile("module.luac", bytecode, 0644)

// Later, load from file
bytecode, _ := ioutil.ReadFile("module.luac")
L.LoadAndRunBytecode(bytecode, "module")

Closures and Upvalues

Bytecode properly preserves closures and upvalues:

code := `
	local counter = 0
	return function()
		counter = counter + 1
		return counter
	end
`

bytecode, _ := L.CompileBytecode(code, "counter")
L.LoadAndRunBytecodeWithResults(bytecode, "counter", 1)
L.SetGlobal("increment")

// Later...
results, _ := L.CallGlobal("increment")  // Returns []any{1}
results, _ = L.CallGlobal("increment")   // Returns []any{2}

Batch Execution

Execute multiple statements efficiently:

statements := []string{
	"x = 10",
	"y = 20",
	"result = x + y",
}
err := L.BatchExecute(statements)

Package Path Management

Manage Lua module paths:

L.SetPackagePath("./?.lua;./modules/?.lua")
L.AddPackagePath("./vendor/?.lua")

Type Conversion System

The wrapper includes a comprehensive type conversion system:

// Get typed values with automatic conversion
value, ok := luajit.GetTypedValue[int](L, -1)
global, ok := luajit.GetGlobalTyped[[]string](L, "myArray")

// Convert between compatible types
result, ok := luajit.ConvertValue[map[string]int](someMap)

Performance Tips

  • Use bytecode for repeated execution
  • Prefer CallGlobal() for simple function calls
  • Use typed field accessors for configuration parsing
  • Leverage automatic type detection in ToTable()
  • Pool your Lua states for high-throughput scenarios

Need Help?

Check out the tests in the repository - they're full of examples. If you're stuck, open an issue! We're here to help.

License

MIT Licensed - do whatever you want with it!

Description
Go wrapper for LuaJIT.
Readme MIT 2.2 MiB
2025-06-06 17:38:59 -05:00
Languages
Go 96.1%
Shell 2.7%
Lua 1.2%