Open-source project
yuin/gopher-lua avatar
yuin/gopher-lua

GopherLua: embedding a Lua 5.1 VM in a Go program

GopherLua: VM and compiler for Lua in Go

6,987 stars708 forksGoMIT

At a glance

What is it?
GopherLua is a Lua 5.1 VM and compiler written in Go, aimed at Go programs that need a small embedded scripting layer. It installs with one module path, but its API is not the stack-based API of the C implementation, and that trade-off shapes everything else.
Who is it for?
Adopt GopherLua when you control the host program, want Lua 5.1 semantics with the goto statement from 5.2, and prefer a Go API over CGo. Do not adopt it if you need LuaJIT speed, the full C API surface, or a sandbox you have not configured yourself.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What GopherLua is for, and who ends up using it

The README states the goal plainly: be a scripting language with extensible semantics, and provide Go APIs that let you embed a scripting language in a Go host program. That second half is the actual product. GopherLua is not primarily a tool you run; it is a library you link into something else, so the user is a Go developer who wants configuration, rules, plugins or user-supplied logic to live outside the compiled binary.

The version matters. GopherLua implements Lua 5.1, plus the goto statement that arrived in Lua 5.2. If your scripts were written for Lua 5.3 or 5.4, integer division, bitwise operators and other later additions are not part of the target. The README even carries a section titled Differences between Lua and GopherLua, which is a signal worth taking seriously rather than a footnote.

The design principle section makes a choice explicit. The original Lua implementation uses a stack-based API, and the README says that approach improves performance by reducing memory allocations and concrete type to interface conversions. GopherLua deliberately does not follow it, and the README says it gives preference to user-friendliness over performance. That sentence tells you which kind of project this is before you write a line of code.

The LValue data model and how values cross the boundary

Everything in a GopherLua program is an LValue, an interface with String() string and Type() LValueType. The concrete types are LNilType, LBool, LNumber, LString, LFunction, LUserData, LState, LTable and LChannel. Most map to obvious Go types: LNumber is a float64, LString is a string, LTable and LUserData are struct pointers.

There is a trap in the type system that the README calls out directly. LBool, LNumber and LString are not pointers, and to test LNilType and LBool you must use the predefined constants. The README shows the wrong version and labels it wrong: checking lv.(lua.LBool) and converting with bool(bl) does not work, while comparing against lua.LTrue does.

The stack still exists, but only for passing arguments and receiving returned values. Values are read with L.Get(-1) and then tested either by Go type assertion or by comparing lv.Type() against a constant such as lua.LTString. Lua's truthiness rules are exposed through helpers: lua.LVIsFalse treats nil and false as false, and lua.LVAsBool is true when a value is neither nil nor false. If you have written C Lua bindings, this is the part that will feel unfamiliar, because you are working with objects rather than stack indices.

Installing GopherLua and running a first script

The README gives exactly one installation step: go get with the module path. The repository's go.mod confirms the module is github.com/yuin/gopher-lua and declares go 1.23, so a Go toolchain at that language version or newer is the floor.

bash
go get github.com/yuin/gopher-lua

Import the package and run a script from a string. The README's example creates a state, defers Close, and calls DoString with a print call. If the script returns an error, the example panics.

go
import (
    "github.com/yuin/gopher-lua"
)

L := lua.NewState()
defer L.Close()
if err := L.DoString(`print("hello")`); err != nil {
    panic(err)
}

The same pattern works against a file on disk with DoFile instead of DoString. The README shows it with hello.lua as the path.

go
L := lua.NewState()
defer L.Close()
if err := L.DoFile("hello.lua"); err != nil {
    panic(err)
}

For a command-line session rather than an embedded one, the repository ships a standalone interpreter under cmd/. The Makefile has a glua target that builds cmd/glua/glua.go, and the README has a Standalone interpreter section. Note what the build target does first: it runs _tools/go-inline over the top-level Go files, then go fmt, then go build. That inlining step is part of how the project builds itself, so building from a source checkout is not the same as consuming the module.

Tuning the registry and callstack, and what happens when you do not

Each LState carries a registry and a callstack, and both can be fixed or auto-sized. The registry is the stack storage for calling functions, both Lua and Go, plus temporary variables in expressions. The callstack controls the maximum call depth for Lua functions; Go function calls do not count toward it.

The README gives the tuning knobs as fields on lua.Options. RegistrySize is the initial size, RegistryMaxSize is the ceiling for growth, and RegistryGrowStep is the increment, defaulting to 32. Setting RegistryMaxSize to 0, the default, means the registry will not grow at all.

go
L := lua.NewState(lua.Options{
   RegistrySize: 1024 * 20,
   RegistryMaxSize: 1024 * 80,
   RegistryGrowStep: 32,
})
defer L.Close()

The failure modes are stated without hedging. A registry too small for a given script will ultimately result in a panic. A registry too big wastes memory, which the README notes can be significant when many LStates exist in one process. Auto-growing registries take a small performance hit at each resize. That combination is the real operational constraint here: if you plan to create a state per request or per tenant, you are choosing between a panic on unusual input and memory you pay for on every instance. The README says it is worth tuning these options when many LStates are instantiated in a process, and the registry does not shrink after growing.

Where GopherLua is the wrong tool

The README does not claim speed. It says GopherLua is not fast but not too slow, and that it has almost equivalent or slightly better performance than Python3 on micro benchmarks, pointing at a wiki page for the numbers. That is a modest claim, and it is the honest frame for adoption: this is a scripting layer for host programs, not a compute engine.

If your workload is Lua-heavy, with scripts doing the real work rather than gluing Go code together, the interpreter ceiling becomes your ceiling. There is no JIT here, and the design principle section says the non-stack API was chosen at some cost to performance. A project that needs LuaJIT-class throughput should not start here.

There is a second boundary that is easy to miss. The README notes that objects based on Go structs, specifically LFunction, LUserData and LTable, have public methods and fields you can use for performance and debugging, but with limitations: metatable does not work, and there are no error handlings when you go through those fields. Reaching into the object model to go faster means leaving the semantics Lua guarantees. That is a real trade, not a documentation gap.

Finally, embedding a scripting language is an exposure decision. The README describes the VM and the API; it does not present a sandbox, a permission model or a resource quota system. The registry and callstack options bound memory and call depth, and that is what the material actually offers.

GopherLua against the alternatives Go developers weigh

The most common comparison is against go-lua, the Lua VM that came out of Shopify and is written in Go as well. Both let a Go program run Lua without CGo. The difference is in the API philosophy. GopherLua's README states that its API is not the stack-based API of the original Lua implementation and that it prefers user-friendliness over performance; you work with LValue objects and type assertions. go-lua follows the C Lua API conventions much more closely, which means existing Lua C binding knowledge transfers and stack discipline is part of the programming model. If you are porting C Lua modules or your team already thinks in stack indices, that familiarity is worth more than GopherLua's ergonomics.

The other direction is not embedding Lua at all. If the scripts are configuration-shaped and never need control flow, tables or metatables, a JSON or YAML decoder plus a small expression evaluator is less machinery. GopherLua earns its place when users need real Lua: functions, tables, closures, and the standard libraries the repository implements across baselib.go, stringlib.go, tablelib.go, mathlib.go, oslib.go and iolib.go.

One practical point on the Lua version. GopherLua targets Lua 5.1 plus goto. Projects that have already moved their scripts to 5.4 syntax will face a porting pass, and the README's own Differences section is where that work starts.

Maintenance, releases and the MIT licence

The repository is not archived. The last push was on 2026-04-01, and the release list shows v1.1.2 on 2026-04-01, v1.1.1 on 2023-12-03 and v1.1.0 on 2023-01-22. The gap between v1.1.0 and v1.1.1 is roughly eleven months, and the gap between v1.1.1 and v1.1.2 is over two years. A tag is not a promise of cadence, but that spacing is the pattern to plan around: pin a version and expect to carry patches yourself rather than waiting on a release.

The dependency surface is small, which helps. go.mod requires github.com/chzyer/readline for the standalone interpreter, with logex and test as indirect dependencies and golang.org/x/sys as another indirect entry. The pinned readline revision is from 2018 and the x/sys revision from early 2019, so if you embed only the library and never build cmd/glua, you are not pulling the readline line into your build graph in a meaningful way.

The licence is MIT. That is permissive and compatible with closed-source hosts. This is not legal advice, and the obligations you actually carry depend on how you distribute the binary and whether you modify the source, so read LICENSE in the repository root rather than relying on the label.

Upgrade cost is dominated by the Lua version target. Because GopherLua implements Lua 5.1 with the 5.2 goto statement, an upgrade is not a path toward newer Lua semantics. There is no documented migration guide in the README for moving scripts between Lua versions, and the README does not document rollback procedures for a failed upgrade.

Editorial conclusion

Adopt GopherLua when you control the host program, want Lua 5.1 semantics with the goto statement from 5.2, and prefer a Go API over CGo. Do not adopt it if you need LuaJIT speed, the full C API surface, or a sandbox you have not configured yourself. Before writing code, read the Options struct for RegistrySize, RegistryMaxSize and RegistryGrowStep, and confirm which Lua 5.1 features your scripts rely on, because the README states there are differences between Lua and GopherLua.

Frequently asked questions

What is GopherLua used for?

It is a Lua 5.1 VM and compiler written in Go, with the goto statement from Lua 5.2, that provides Go APIs for embedding a scripting language in a Go host program. The README states the goal is a scripting language with extensible semantics.

How do I install GopherLua?

The README gives a single step: go get github.com/yuin/gopher-lua. The module's go.mod declares go 1.23, so your toolchain needs to support that language version.

What is the difference between GopherLua and the original Lua?

GopherLua's API is not the stack-based API used in the original Lua implementation; the README says it gives preference to user-friendliness over performance. The stack is used only for passing arguments and receiving returned values, and the README has a dedicated section on differences between Lua and GopherLua.

Does GopherLua come with a standalone interpreter?

Yes. The README has a Standalone interpreter section, and the repository contains cmd/glua/glua.go, which the Makefile builds with the glua target.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. yuin/gopher-lua on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yuin-gopher-lua.svg)](https://hysenlabs.com/projects/yuin-gopher-lua)