milochristiansen/lua, a Lua 5.3 virtual machine written in Go
A Lua 5.3 VM and compiler written in Go.
At a glance
- What is it?
- The milochristiansen/lua project is a Lua 5.3 VM and compiler in Go, built for embedding into Go programs, with the standard library split into separate packages and no OS or file access in the core. Its compiler is close to luac but not identical, and the author is candid about where it falls short.
- Who is it for?
- milochristiansen/lua suits a Go project that wants to embed a scripting language with a small, auditable API and no file or OS access for the scripts, and it does not suit a team that needs the reference compiler's exact output or the parts of the standard library this VM omits.
- Can I use it commercially?
- Yes. Zlib 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?
- Yes. The repository last received commits 98 days 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 October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A Lua VM for Go programs, with the library left out
The milochristiansen/lua repository, headed as DCLua in the README, is a Lua 5.3 VM and compiler written in Go. The stated purpose is to allow easy embedding into Go programs with minimal fuss, and the module path is github.com/milochristiansen/lua.
The design choice that defines the project is what the VM deliberately does not contain. The VM itself provides no Lua functions at all. The standard library is provided entirely by other packages, and the reason given is that the standard library then never does anything your own code cannot do, with no private API behind it. In the repository layout those packages are visible as separate directories: lmodbase, lmodmath, lmodpackage, lmodstring, lmodtable and lmodutf8, each of which can be imported on its own.
The second boundary is OS and file access. Anything to do with the operating system or file IO is not provided, and the reasoning given is that such things do not belong in the core libraries of an embedded scripting language, with the pointed question of whether you really want scripts to read and write random files without restriction. For a game tool or a build step that wants to embed scripting without exposing the host, that is a sensible default rather than a missing feature.
The evidence offered for the implementation's fitness is real-world rather than aspirational. The author used this VM and compiler as the primary script host in Rubble, a scripted templating system used to generate data files for the game Dwarf Fortress, for over a year, and proper tests based on the official Lua test suite are being added slowly. The README says those tests are far from complete but are getting more so over time.
Panics are the error model, and one prefix means a VM bug
The error handling model is unusual and worth understanding before you call into the VM from Go.
Most, if not all, of the API functions may cause a panic, but only if things go really wrong. The distinction is whether the function documents that it can panic or raise an error. If it does not, it will only do so if a critical internal assumption proves to be wrong, in other words if there is a bug in the code somewhere. Those errors carry a special prefix prepended to the message, stating that the error indicates an internal VM bug, and the author asks to be told about them as soon as possible.
If a function can raise an error, it can and will panic when something goes wrong. Inside a native function this is not a problem, because the VM is prepared for it. Outside of code that the VM itself runs, though, you may want to use Protect or Recover to catch these errors properly.
That gives a workable convention for a Go host. Treat a panic with the internal-bug prefix as a signal that something is wrong with the VM rather than with the script, and treat the rest as a script-level error that Protect and Recover can turn into a Go error. The alternative, converting every Lua error path into Go errors automatically, would have cost the simplicity of the API, and the author notes that everything was added because it was needed and there are no bloat functions added speculatively.
The documentation coverage is described as consistent rather than perfect. All functions, including most of the internal ones, are documented to one degree or another, most of them quite well.
Precompiled chunks have to match a specific set of settings
The VM fully supports binary chunks, so a script can be precompiled. There is no separate compiler binary in this project, and the README says writing one would not be hard, but for now you build a copy of luac, the reference Lua compiler, or use another third-party Lua compiler that produces code compatible with the reference compiler.
There is a compatibility warning attached to that. The VM does not handle certain instructions in pairs the way the reference Lua VM does, and the author does not remember whether the compiler was written to take advantage of that. If it was, binaries produced by this compiler may not work with the reference VM.
A third-party compiler has to produce binaries with a specific set of settings: 64 or 32 bit pointers of the C type size_t, with 64 bit preferred, 32 bit integers of the C type int, 64 bit float numbers, 64 bit integer numbers, and little endian byte order. When building the reference compiler on most systems those settings are the defaults.
There are two ways to load code. The VM API has a function that wraps luac to load code, described in the documentation for State.LoadTextExternal. Using it means luac has to be on your path or otherwise findable by the VM, and because of limitations in Go and luac this function is not reentrant. If you need concurrency support, the README advises using State.LoadBinary and writing your own wrapper.
The compiler is close to luac, and honest about the gap
The default compiler shipped with the library is not identical to luac, and the README describes the differences in detail.
It does not support constant folding, and some special instructions are not used at all, with simpler sequences of other instructions preferred instead. Expressions use a simple recursive code generation style, which wastes registers in some, rare, cases.
The worst offender, by the author's own account, is the handling of or and and. The README shows the generated sequence for a condition, several instructions long with a pair of LOADBOOL and a TEST, and contrasts it with the shorter sequence luac produces. The reason given is that the expression used or, and that the implementation of and and or is very bad. Most other things produce code that is very close to or identical to what luac produces.
There is one case where this compiler does better. When compiling loops or conditionals with constant conditions, impossible conditions are elided, so a while false do statement compiles to nothing. As far as the author knows there is no way to jump into such blocks anyway, so eliding them should have no effect on correctness.
Taken together, the picture is of a compiler that is good enough for most scripts and wrong in a specific, identifiable way for expressions using or and and. If you precompile and ship bytecode, that is the thing to test against your actual scripts.
Features left in the code on purpose
Two capabilities exist in the tree but are switched off, and the README explains how to enable them.
The first is a continue keyword. The compiler provides an implementation of it, but the keyword definition in the lexer is commented out. If you want continue, all you need to do is uncomment the indicated line, which the README locates near the top of ast/lexer.go.
The second is zero-based table indexing. There is a flag in the VM that should make tables use 0 based indexing, but the feature has received minimal testing and probably does not work properly. Setting the variable TableIndexOffset to 0 turns it on. The important caveat is that TableIndexOffset is strictly a VM setting, and the standard modules do not respect it, so the table module and ipairs will still insist on using 1 as the first index.
That caveat is the whole story for anyone considering the option. A language where the VM indexes from zero but the library assumes one is a source of subtle bugs rather than a feature, and the README says so plainly. The continue keyword has no such problem, since it is a compiler-level change that applies consistently to the code it compiles.
What is missing, and the sibling implementation to compare
The README ends with a list of standard functions and variables that are not available, and the reasons are given for each. collectgarbage is not possible because the VM uses the Go collector. dofile and loadfile are excluded because they violate the author's security policy.
That list is the practical edge of the library. A script that expects to load a file at runtime will not find the usual function for it, and anyone porting an existing Lua corpus will hit the missing pieces. The mitigation is that the standard library lives in separate packages, so an application can add its own controlled version of file access rather than granting it by default.
The README also points at another implementation of the same idea, ofunc/lua, saying that version has some interesting changes and features and suggesting it might suit a reader's needs better. That is a fork relationship rather than a competitive one, and the difference in approach is that the sibling carries additional features while this one stays closer to the shape described here.
For the practical question of where this fits: a Go service that wants to run user-supplied configuration or content scripts, a game tool that needs a scripting layer without giving scripts the host filesystem, or a tool that must produce Lua-compatible behaviour from Go code without shelling out to a Lua binary. In all three cases the missing standard library pieces are the reason it works. A project that wants the reference Lua environment in Go, with the full library and a byte-identical compiler, is looking for a different implementation.
Editorial conclusion
milochristiansen/lua suits a Go project that wants to embed a scripting language with a small, auditable API and no file or OS access for the scripts, and it does not suit a team that needs the reference compiler's exact output or the parts of the standard library this VM omits. Before you adopt it, read the missing functions list in the README, since collectgarbage, dofile and loadfile are absent by policy and the standard library ships as separate packages, and note that the newest tagged release is v1.1.7 from 2019 while the last push was on 2026-07-17, so you would be tracking master.
Frequently asked questions
What is milochristiansen/lua?
It is a Lua 5.3 virtual machine and compiler written in Go, intended to make it easy to embed scripting into Go programs with minimal fuss. The VM itself provides no Lua functions, since the standard library is supplied by separate packages such as lmodstring, lmodmath and lmodutf8.
How does the Lua VM in Go report errors to Go code?
Most API functions may panic when something goes wrong, and errors that indicate a broken internal assumption carry a special prefix saying so, which the author asks to be reported. For functions documented as able to raise an error, the README suggests using Protect or Recover to catch them properly when calling from outside VM-run code.
Can I precompile Lua scripts and load them into this VM?
Yes, the VM fully supports binary chunks. There is no separate compiler binary in the project, so you use luac or another compatible third-party compiler, and the binary must use 64 or 32 bit size_t pointers, 32 bit ints, 64 bit floats, 64 bit integers and little endian byte order.
Which standard library functions are missing from this Lua VM?
collectgarbage is not available because the VM uses the Go collector, and dofile and loadfile are excluded by the author's security policy. Anything to do with the operating system or file IO is also not provided.
Can I use continue or zero-based table indexing with this Lua VM?
Both exist in the code but are switched off. The continue keyword needs its definition in the lexer uncommented, near the top of ast/lexer.go, and zero-based tables need the TableIndexOffset VM variable set to 0, though that feature has received minimal testing and the standard modules do not respect the setting.
Official sources
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.
[](https://hysenlabs.com/projects/milochristiansen-lua)Was this useful?