Side-by-side, interactive cheatsheets for Lua programmers
comparing Lua to other languages. Every example runs live in your browser — no setup, no installation.
Choose your own path by reordering languages
The closest language to Lua on this whole site. Two small dynamic languages that grew objects out of a lookup fallback rather than a class declaration — and, uniquely among the targets here, Ruby agrees with Lua about what is true.
nil and false are falsy — in both. 0 and "" stay true, so every truthiness guard you have written means the same thing__index vs method_missing), and Ruby 4.0 froze string literals so strings are immutable in bothyield to call itnil to a Hash key does NOT delete it, which is the one place nil parts company with Luanil have methods, and Enumerable gives you the map/select/reduce Lua leaves you to writedef opens a fresh scope and cannot see surrounding locals — blocks and lambdas still close over themYour closest living cousin. Both are small dynamic languages built to be embedded in a host, both grew objects out of a lookup fallback rather than declaring classes, and both have real closures. The family resemblance is why the handful of differences catch people.
0 and "" change sides, so every if value then guard needs rereadingnil becomes two values, null and undefined, and assigning one no longer deletes a keyself is a parameter the colon fills in; this is decided by the call site, so a detached method loses it__index maps over almost exactly, but there is no operator overloadingawait is not coroutine.yield: there is an event loop underneathmap/filter/reduce Lua leaves you to writeWhat Lua would look like if it had stopped saying no. Both are dynamic, garbage-collected and interpreted with first-class functions and closures — then Lua draws a hard line at 250 KB of interpreter, and Python says yes to everything.
0, "", [] and {} are all falsy, so a zero check means the opposite of what it does in Luanil, and assigning None no longer deletes it__index, __newindex, __add, __call, __tostring all have counterpartsself is an explicit first parameter, exactly what Lua's colon has been inserting for you all along// from Python — but assigning to a captured variable needs nonlocalThe language your interpreter is written in. Lua exists to be embedded in a C program, so C is not a foreign country — it is the host you already live inside. This is the trip across that boundary, from the guest's side.
malloc and free, with leaks, double frees, and use-after-free all compiling silently0 is false in C, the exact inverse of the rule you know — and if (strlen(name)) means the opposite of what its Lua shape suggests#text becomes an O(n) strlen and == compares addresses instead of contentspcall: errors come back as return codes you must check at every level, with no stack unwindingrequire can load, with lua_State, the virtual stack, and luaL_check*The language people embed Lua from today. mlua is what a Neovim plugin, a Redis module or a game's mod host reaches for, so this is the modern counterpart of the C page — same boundary, a host language that replaced C for new work. One idea explains every strange thing about it: there is no garbage collector, so the compiler tracks who owns each value.
Rc<RefCell<T>> is how you spell a Lua table: one value, several names, all able to mutate it — and writing it out shows exactly what that convenience costsnil becomes Option<T>, so a missing key is a value you must open rather than one that travels three functions before failingif 0 is a type error, so the falsy-set question goes away instead of changingcoroutine.wrap becomes an iterator and two-way resume becomes a struct you write by handA type layer over the cousin you already know. Read /lua/javascript first — this page is not that page with types bolted on. What is left is the checking: describing what Lua leaves implicit, and having it erased before anything runs.
strictNullChecks is the answer to the nil that travelled three modules before it crashed — absence becomes part of the return typekind field, and never makes the compiler find every unhandled caseas is a promise rather than a conversionThe language your host program is most likely to become — Unity scripts are C#. It is also the furthest thing from Lua on this anchor: static nominal types, a compile step, and a standard library larger than Lua's whole ecosystem. Coroutines are the bridge, because Unity resumes an IEnumerator once per frame exactly as an update loop resumes yours.
bool, so if (0) is a compile error rather than a surpriseList<T>, Dictionary<K,V>, tuple, record, and a struct that copies on assignment__index is an indexer or a property, __add is operator +, and __eq is what record gives you freecoroutine.wrap is yield return and coroutine.yield is await — the same machinery, with a scheduler you no longer writeyield return, where coroutine.resume always couldWhere a Lua game developer looks next. LÖVE, Defold and Roblox all script in Lua, and Godot is the obvious step across. The syntax is a short trip; the architecture is not — you stop owning the game loop.
_process(delta), and nothing iterates your entity list0, "" and empty collections are all falsy, so a zero-health check means the opposite of what it does in Lua7 / 2 is 3 — two integers divide as integers, which bites hardest when centering somethingArray and Dictionary, and reading a missing key raises instead of giving you nilend; and there is no accidental global, because every name must be declaredmatch that destructures, and signals instead of polling a flag every framepcall and no exceptions at all — you check before acting, or return an error code