Technical Beauty ■ Episode 18
“We could have imported software. Brazil decided we could not. So we wrote our own.”
In 1984, Brazil’s military government enforced the Política Nacional de Informática, a market reserve policy that banned the import of foreign software where a domestic equivalent existed, or might conceivably exist, or could be imagined to exist on a sufficiently optimistic afternoon. The intent was to foster a national technology industry. The immediate effect was that three researchers at the Pontifical Catholic University of Rio de Janeiro could not obtain the tools they needed. They were consulting for Petrobras, the state oil company, and the data-entry language they required was simply unavailable. Import licence: denied.
So Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar Celes did what engineers do when bureaucracy blocks the obvious path. They built two data-description languages: SOL (Simple Object Language) and DEL (Data Entry Language). By 1993, the two had converged into a single, general-purpose embeddable scripting language. They named it Lua (Portuguese for “moon”) because it orbited the host application rather than standing alone. SOL means “sun” in Portuguese. The moon orbits the sun. Naming things, it turns out, is not always the hardest problem in computer science.
The Constraints
The numbers are worth stating plainly, because they sound like misprints. The Lua 5.4 reference implementation is approximately 25,000 lines of ANSI C. The compiled binary is around 200 KB. The runtime memory footprint begins at roughly 300 KB. These are not targets the team aimed for and approximately hit. These are the natural consequence of a design philosophy that treats every line of code as a liability and every kilobyte as an extravagance.
For context: the Python runtime is approximately 50 MB. Google’s V8 JavaScript engine (the engine, not the browser) comprises roughly two million lines of C++. Both are excellent tools. Neither can be embedded in a game engine without the game engine noticing.
The Embedding
To embed Lua in a C application, one includes a single header file,
links a single library, and calls
lua_pcall(). That is the complete integration story. There
is no package manager. There is no build system dependency. There is no
initialisation ritual involving twelve configuration objects, a factory
pattern, and a prayer. The
C API
communicates through a virtual stack: push values, call functions, pop
results. The stack is the interface. The interface is the stack.
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "print('hello')");
lua_close(L);
Eight lines. A complete, functioning embedded scripting environment. Compare this with embedding Python, which requires initialising the interpreter, configuring the module search path, managing the Global Interpreter Lock, and navigating a reference-counting API that has produced more memory leaks than contributions to human knowledge. Or embedding V8, which requires understanding isolates, contexts, handles, handle scopes, and a compilation pipeline that would make a petrochemical refinery look straightforward.
Lua’s C API is small because the language is small. The language is small because the designers understood that an embeddable language must be smaller than the thing it is embedded in. A scripting engine that dwarfs its host has confused the roles of moon and planet.
The Adopters
The roster of software that embeds Lua reads less like a technology list and more like a cross-section of computing itself.
World of Warcraft exposes its entire user interface to Lua. Every addon (every damage meter, every raid frame, every auction house extension that has consumed more human hours than some national infrastructure projects) is written in Lua. Blizzard chose it in 2004 because it was small enough to ship with the client, fast enough to run during combat, and sandboxable enough that twelve-year-olds writing addons could not crash the server.
Adobe Lightroom is substantially written in Lua. Not merely scripted by it, written in it. The cataloguing, the UI logic, the preset system. When a photographer imports 40,000 RAW files and the application remains responsive, Lua is the reason the architecture holds.
OpenResty embeds Lua in nginx, turning a web server into a programmable application server. Non-blocking I/O. Coroutines mapped to nginx’s event loop. Request processing in microseconds, not milliseconds. Cloudflare runs its edge network on this. When your DNS query resolves, there is a reasonable chance Lua touched it.
Redis
embeds Lua for server-side scripting. The EVAL command
executes Lua scripts atomically: no other command can interleave
during execution. This turns Redis from a data structure server into a
programmable transaction engine. Without adding a transaction protocol.
Without adding a query language. Without adding anything except 200 KB
of Lua.
Neovim replaced Vimscript with Lua as its primary extension language. The plugin ecosystem responded by producing configuration frameworks that are, in some cases, more elaborate than the applications they configure. But the core insight stands: Lua is fast enough for an editor that must respond within a single frame of screen refresh.
Roblox created Luau, a typed dialect of Lua. Every game on the platform (and there are millions) runs on it. The next generation of programmers is learning to code in a language descended from a Brazilian trade dispute.
Nmap uses the Nmap Scripting Engine, powered by Lua, to extend network scanning with custom probes and vulnerability checks. Security researchers write detection scripts in a language that adds negligible overhead to the scan.
The Design
Lua achieves its size not by omitting features but by choosing the right primitive. The language has one data structure: the table. A table is an associative array that also serves as an array, a record, an object, a module, a namespace, and, through metatables, a class hierarchy. There is no array type because a table with integer keys is an array. There is no class keyword because a table with a metatable is a class. There is no module system beyond tables because a module is a table.
This is not laziness. It is the same insight that gave Unix the file descriptor: one abstraction, universally applied, is more powerful than twelve abstractions, each special-cased. The table is Lua’s file descriptor. Everything is a table. Everything composes.
The language has first-class functions, lexical scoping, closures, coroutines, and a mark-and-sweep garbage collector, in 25,000 lines. It supports tail-call optimisation. It has a register-based virtual machine (not stack-based, like Java or Python), which produces fewer instructions and better cache locality. The bytecode compiler and the virtual machine share the same codebase, the same memory allocator, and the same error-handling model. There is no seam between compilation and execution because the designers refused to create one.
The Origin
The history, published in the Proceedings of the ACM on Programming Languages, makes instructive reading. The trade barriers were lifted in 1991, but by then Ierusalimschy, Figueiredo, and Celes had already built something that no imported tool could match. Not because Brazilian engineering was superior to American engineering, but because the constraint itself (build something embeddable, small, portable, with no external dependencies) was the right constraint. The market reserve forced them to solve the right problem.
This is a recurring pattern in technical history. The CACM retrospective notes that Lua’s design was shaped not by academic fashion but by the practical needs of Brazilian industry: oil companies, steelworks, geological surveys. These clients did not want a language. They wanted a configuration system that could grow into a language if it needed to. Lua grew. It never stopped being a configuration system. That dual nature, simultaneously trivial to embed and powerful enough to build applications, is why it survived.
The Licence
Lua is released under the MIT licence. Not the LGPL. Not the GPL. Not the Apache licence with its patent clauses. The MIT licence. Two paragraphs. Use it, modify it, sell it, embed it, forget to credit us if you must. This is not idealism; it is strategy. An embeddable language succeeds by being embedded. Every licence restriction is a reason not to embed. Every legal clause is a procurement department saying “no”. Lua has no such clauses. Lua has no such problems.
The result is that Lua ships in products whose legal teams would never approve a copyleft dependency. Game consoles. Medical devices. Defence systems. Adobe’s creative suite. The MIT licence is not merely a legal document. It is a deployment mechanism.
The Longevity
Lua was first released in 1993. It is now 33 years old. In programming language terms, this places it comfortably in middle age: younger than C (1972), older than Java (1995), roughly contemporary with Python (1991) and Ruby (1993). But where Python has grown from 12,000 lines to a multi-megabyte ecosystem with a standard library the size of a small encyclopaedia, Lua 5.4 remains 25,000 lines. The growth rate, across three decades, has been approximately linear and approximately flat.
This is not stagnation. The language has added coroutines (5.0), incremental garbage collection (5.1), goto (5.2), integers (5.3), and generational garbage collection (5.4). Each feature was added only when the use cases demanded it and only after extensive deliberation. The Lua mailing list archives reveal a team that says “no” far more often than “yes”. The inbox is full of feature requests. The language is full of restraint.
This is the hallmark of mature engineering. Adding a feature is easy. Refusing to add a feature that would make the language 0.5 per cent more convenient and 2 per cent larger requires an understanding of compound growth that most projects lack. Twenty features at 2 per cent each, and you have doubled your codebase. Lua has not doubled in thirty-three years.
The Proof
The ultimate test of an embeddable language is not whether it can be
embedded, but whether the host application forgets it is there.
When a World of Warcraft player opens the auction house, they are
not aware of Lua. When a photographer adjusts exposure in Lightroom,
they are not aware of Lua. When Cloudflare routes a request through
OpenResty, the latency does not reveal Lua’s presence. When
Redis executes an EVAL script, the atomicity guarantee
holds without the caller knowing the mechanism.
This is the deepest form of technical beauty: a tool so well-designed that it disappears into its purpose. The user sees the host application. The developer sees a clean C API. The language sees 25,000 lines of carefully reasoned code and a binary that fits in the L2 cache of a modern processor.
Three researchers in Rio de Janeiro could not import software. So they built a language that would be imported by everyone else. 25,000 lines. 200 KB. Three decades. The constraint was not the obstacle. The constraint was the design.
Technical beauty is not achieved when there is nothing more to add, but when there is nothing left to take away. Lua has been taking things away for thirty-three years. The binary is still 200 KB.