Lua
Lua is a small scripting language designed to be embedded inside other programs. The entire reference implementation is about 30,000 lines of portable C and compiles to a few hundred kilobytes, which is why it keeps turning up inside game engines, databases, web servers and editors rather than as a standalone application platform.
What Lua actually is
Section titled “What Lua actually is”Lua (“moon” in Portuguese, created in 1993 at PUC-Rio in Brazil) is a dynamically typed, garbage-collected language with:
- One composite data structure. Tables. Arrays, dictionaries, objects, namespaces and modules are all tables. There is no separate list, dict, class, struct or object type.
- First-class functions with lexical scoping. Closures are a core feature, not an add-on.
- Coroutines. Cooperative, stackful coroutines built into the language — the basis for most Lua async libraries.
- Metatables. A small hook system that lets you override what indexing, arithmetic, comparison and calling mean for a value. Object orientation is built on top of this, not into the language.
- A deliberately tiny standard library. String, table, math, os, io, coroutine, and a debug library. No sockets, no JSON, no HTTP, no regex engine.
That last point is the key to understanding Lua. It is not a batteries-included language, and it is not trying to be. It is a language kernel that a host application embeds and then extends with whatever the host already knows how to do.
Where you will meet it
Section titled “Where you will meet it”Lua is almost always the scripting half of a larger program:
| Host | What Lua does there | Version |
|---|---|---|
| Neovim | Configuration, plugins, and most of the modern plugin ecosystem | LuaJIT (5.1 + some 5.2) |
| Redis | Server-side scripts run atomically via EVAL / FUNCTION |
Lua 5.1 |
| OpenResty / nginx | Request handling, routing, auth, rate limiting (also under Kong, APISIX) | LuaJIT |
| Roblox | Game logic | Luau, a typed Lua 5.1 derivative |
| LÖVE (love2d) | The whole game — LÖVE is a 2D game framework that is a Lua host | LuaJIT |
| World of Warcraft | Addons and UI | Lua 5.1 |
| Wireshark, mpv, VLC, Nmap, HAProxy | Plugins, dissectors, filters, scripts | varies |
| Awesome WM, Conky, Hammerspoon | Desktop configuration and automation | varies |
Standalone Lua exists too (lua on the command line, plus the LuaRocks package manager), but embedding is the dominant use.
Versions: the part that actually bites
Section titled “Versions: the part that actually bites”Lua does not promise backwards compatibility between minor versions. 5.1, 5.3 and 5.4 are meaningfully different languages, and the version is decided by whatever program is embedding Lua — not by you.
| Version | Released | What matters |
|---|---|---|
| 5.1 | 2006 | Still everywhere because LuaJIT froze here. No goto, no integers, unpack is global, modules via module(), setfenv/getfenv exist. |
| 5.2 | 2011 | Added goto, _ENV (replacing setfenv), table.unpack, package.searchers. |
| 5.3 | 2015 | Added an integer subtype and bitwise operators (&, |, ~, <<, >>), // floor division, string.pack. |
| 5.4 | 2020 | Generational GC, <const> and <close> variable attributes, warn(), coroutine.close, stricter numeric for. Current stable line. |
| LuaJIT 2.1 | ongoing | A tracing JIT compiler for the 5.1 language plus a few 5.2 features (notably goto). Adds the ffi, bit and jit libraries. Often 10–50× faster than the reference interpreter on numeric code. All numbers are doubles — there is no integer subtype. |
Find out what you have:
lua -v# => Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rioprint(_VERSION) -- => Lua 5.4print(jit and jit.version) -- => LuaJIT 2.1.x, or nil on standard LuaRunning Lua
Section titled “Running Lua”Install it from your package manager (lua5.4, lua, luajit) or from lua.org — building from source is a two-minute make.
# Interactive REPLlua
# Run a scriptlua hello.lua
# Run a one-linerlua -e 'print(("hi"):upper())'# => HI
# Load a file, then drop into the REPL with its globals availablelua -i setup.lua
# Pass arguments — they land in the global table `arg`lua script.lua one twoprint(arg[0]) -- => script.lua (the script's own name)print(arg[1]) -- => oneprint(#arg) -- => 2In the REPL, Lua 5.3+ prints the value of a bare expression:
$ luaLua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio> 2 + 35> ("abc"):rep(2)abcabcThere is also luac, which compiles a script to bytecode (luac -o out.luac in.lua). Bytecode is version- and platform-specific and is not a security boundary — never load() untrusted bytecode.
A first script
Section titled “A first script”Enough of the language to recognize its shape:
local counts = {}
for line in io.lines(arg[1]) do for word in line:lower():gmatch("%a+") do counts[word] = (counts[word] or 0) + 1 endend
-- Tables are unordered, so collect the keys and sort them.local words = {}for word in pairs(counts) do words[#words + 1] = wordendtable.sort(words, function(a, b) return counts[a] > counts[b] end)
for i = 1, math.min(#words, 5) do print(string.format("%5d %s", counts[words[i]], words[i]))endlua wordcount.lua /usr/share/dict/wordsAlmost every Lua trait is visible here: local everywhere, one table doing duty as both a dictionary and a list, counts[word] or 0 standing in for a default value, a gmatch pattern instead of a regex, an anonymous comparator function, and 1-based indexing.
Why it gets embedded
Section titled “Why it gets embedded”The whole point of Lua is the C API. A host program creates a lua_State, opens whichever libraries it wants to expose, and exchanges values with Lua through a virtual stack. Embedding is roughly this much code:
#include <stdio.h>#include <lua.h>#include <lauxlib.h>#include <lualib.h>
int main(void) { lua_State *L = luaL_newstate(); /* a fresh, independent interpreter */ luaL_openlibs(L); /* expose the standard library */
if (luaL_dofile(L, "plugin.lua") != LUA_OK) fprintf(stderr, "lua error: %s\n", lua_tostring(L, -1));
lua_close(L); return 0;}cc host.c $(pkg-config --cflags --libs lua5.4) -o hostThree properties make this practical, and they explain most of Lua’s design:
- A
lua_Stateis fully self-contained. No globals in the C library, so a program can run many independent interpreters, one per request or per game entity. - Errors never escape into C. They unwind to the nearest protected call, so a broken script cannot take down the host.
- Everything is a value the host can inspect and control. The host chooses which libraries exist — Redis and most game engines simply do not load
ioandos.
That is why the standard library is small: anything the host already does well (networking, threads, rendering, file layout) is the host’s job to expose, not the language’s job to duplicate.
Design philosophy
Section titled “Design philosophy”Lua’s authors describe the guiding rule as “mechanisms, not policies”, and the consequences are everywhere:
- One data structure. Rather than list, dict, set, object and record types, you get the table, and conventions on top. Less to implement, less to learn, less to keep compatible.
- No built-in object system. Metatables let you build the class model you want — prototypes, single inheritance, multiple inheritance, traits — and every framework does.
- Cooperative concurrency only. Coroutines give you the control flow; the host supplies the scheduler and the I/O.
- Portability over features. The reference implementation is ISO C, with no dependencies beyond libc, which is why Lua runs on microcontrollers, consoles and mainframes alike.
- Freedom to break compatibility. Minor versions change the language. That is unusual and occasionally painful, but it is why Lua has stayed small for thirty years instead of accreting.
Performance follows from the same minimalism: a register-based VM, an incremental (5.4: optionally generational) garbage collector, interned short strings, and tables with a dense array part. Standard Lua is among the fastest interpreted languages; LuaJIT is faster still, often within a small factor of C on numeric code.
Ecosystem
Section titled “Ecosystem”LuaRocks is the package manager. Modules are pure Lua or C extensions, and the notable ones are small and focused:
luarocks install lpeg # parsing expression grammars, by Lua's authorluarocks install penlight # general-purpose "missing" stdlibluarocks install luasocket # TCP/UDP/HTTPluarocks install busted # test frameworkluarocks install luacheck # static linter -- catches accidental globalsAdd --local to install into ~/.luarocks instead of system-wide, then run eval "$(luarocks path)" so package.path picks it up.
Inside a host application, ignore all of this: use the host’s own module system and whatever it bundles. Neovim, OpenResty and Roblox each have their own conventions and their own standard library extensions.
The mental model
Section titled “The mental model”Four ideas carry most of Lua:
- Everything composite is a table, and tables have an array part and a hash part that you never see directly.
- Values are dynamically typed; variables are not typed at all.
local xcan hold anything, including a function or a table. - Metatables are the extension point. When an operation on a value doesn’t have a default meaning, Lua looks for a metamethod. That single rule gives you OOP, operator overloading, read-only tables, default values, and proxies.
- Multiple return values are a real thing, not a tuple.
local a, b = f()andf()returning three values in the middle of a list are different situations with different rules.
Lua is also unusual in what it doesn’t have: no continue, no switch, no exceptions with try/catch (you get pcall), no classes, no +=, no ternary operator, no built-in string interpolation, and 1-based indexing.
How this section is organized
Section titled “How this section is organized”- Basics — the eight types, dynamic typing,
localvs global (and why it matters more here than elsewhere),nil, numbers and the integer/float split, truthiness. - Control flow and functions — conditionals, all four loop forms,
goto, operators and theand/oridioms, multiple returns, varargs, closures, and the:method syntax. - Tables — the one data structure: arrays, dictionaries, the
#operator and its holes, iteration order, metatables and every metamethod worth knowing, OOP and inheritance, the module pattern. - Strings and patterns — the string library,
string.format, and Lua patterns in depth. Patterns look like regex and are not regex; this page is mostly about that. - Standard library and idioms —
table,math,os,io; error handling witherror/assert/pcall/xpcall;requireand the module search path; coroutines; and the gotcha list.
Read them in order the first time. Tables and patterns are where Lua differs most from what you already know.
Key points
Section titled “Key points”- Lua is a ~250 KB embeddable language kernel, not an application platform. The host program supplies the rest.
- The version is chosen by the host. 5.1/LuaJIT is still extremely common; 5.4 is the current reference line.
- Tables are the only composite type; metatables are the only extension mechanism.
- Check
_VERSIONandjitbefore assuming which dialect you are writing.