PONYΞ»M2Modula-2
CodeCompared
for Lua programmers

You already know Lua.Now explore other languages.

Side-by-side, interactive cheatsheets for Lua programmers
comparing Lua to other languages. Every example runs live in your browser β€” no setup, no installation.

β–Ά Start with RubyBrowse comparisons ↓Explore the language map β†—

Choose your own path by reordering languages

Ruby⚑ Works Offline⚑ Offline

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.

  • βœ… Only nil and false are falsy β€” in both. 0 and "" stay true, so every truthiness guard you have written means the same thing
  • βœ… Coroutines and Fibers are near-identical, down to two-way value passing and the same names
  • βœ… Both overload operators, both intercept a missing member (__index vs method_missing), and Ruby 4.0 froze string literals so strings are immutable in both
  • 🚨 Blocks are the one big idea with no Lua counterpart β€” an invisible extra parameter with its own syntax and yield to call it
  • 🚨 Assigning nil to a Hash key does NOT delete it, which is the one place nil parts company with Lua
  • Everything is an object, so numbers and nil have methods, and Enumerable gives you the map/select/reduce Lua leaves you to write
  • A def opens a fresh scope and cannot see surrounding locals β€” blocks and lambdas still close over them
JavaScriptAlpha⚑ Works Offline⚑ Offline

Your 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.

  • 🚨 Two falsy values become six β€” 0 and "" change sides, so every if value then guard needs rereading
  • The one table type splits into objects and arrays, with different methods and no mixing
  • nil becomes two values, null and undefined, and assigning one no longer deletes a key
  • self is a parameter the colon fills in; this is decided by the call site, so a detached method loses it
  • Metatables become prototypes β€” __index maps over almost exactly, but there is no operator overloading
  • Coroutines become generators, which is a close match β€” but await is not coroutine.yield: there is an event loop underneath
  • A far larger standard library, starting with the map/filter/reduce Lua leaves you to write
PythonBeta⚑ Works Offline⚑ Offline

What 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.

  • The pattern you hand-roll in Lua is a language feature here β€” classes, exceptions, comprehensions, decorators, a standard library of hundreds of modules
  • 🚨 0, "", [] and {} are all falsy, so a zero check means the opposite of what it does in Lua
  • 🚨 A missing dict key raises instead of giving you nil, and assigning None no longer deletes it
  • One table type splits four ways β€” list, dict, tuple and set β€” and indexing is 0-based with half-open slices
  • Metatables map onto dunder methods almost one for one: __index, __newindex, __add, __call, __tostring all have counterparts
  • self is an explicit first parameter, exactly what Lua's colon has been inserting for you all along
  • Division agrees exactly β€” Lua 5.3 took // from Python β€” but assigning to a captured variable needs nonlocal
CPre-Alpha

The 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.

  • No garbage collector β€” malloc and free, with leaks, double frees, and use-after-free all compiling silently
  • 🚨 0 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
  • No tables: fixed-size, zero-based, same-typed arrays that do not know their own length, plus structs whose fields are fixed at compile time
  • Strings are mutable bytes ending in a zero, so #text becomes an O(n) strlen and == compares addresses instead of contents
  • Function pointers capture nothing β€” closures, and the iterators and callbacks built on them, have no equivalent
  • No pcall: errors come back as return codes you must check at every level, with no stack unwinding
  • And the payoff β€” writing a C function require can load, with lua_State, the virtual stack, and luaL_check*
RustPre-Alpha

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.

  • Ownership is the whole story β€” passing a value can give it away, where passing a table has always just shared it
  • 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 costs
  • nil becomes Option<T>, so a missing key is a value you must open rather than one that travels three functions before failing
  • Truthiness stops existing β€” if 0 is a type error, so the falsy-set question goes away instead of changing
  • Metatables become traits, and the orphan rule is the exact opposite of patching the shared string metatable: two libraries can never collide
  • The honest gap: stable Rust has no generators, so coroutine.wrap becomes an iterator and two-way resume becomes a struct you write by hand
TypeScriptAlpha⚑ Works Offline⚑ Offline

A 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 type
  • Union and literal types replace the table of allowed strings you validate by hand
  • Discriminated unions understand your kind field, and never makes the compiler find every unhandled case
  • Structural typing, so shape is the type β€” duck typing made static, which should feel familiar
  • Generics carry a type through a call; Lua has no way to say a list of strings gives back a string
  • 🚨 The types are ERASED β€” no reflection, no run-time check, and as is a promise rather than a conversion
  • TypeScriptToLua compiles it back down to Lua, so the runtime you already ship stays put
C#Pre-Alpha

The 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.

  • Truthiness stops existing β€” a condition must be a bool, so if (0) is a compile error rather than a surprise
  • LINQ replaces the accumulate-into-a-fresh-table loop that Lua makes you write for every filter, map and group
  • One table becomes many types β€” array, List<T>, Dictionary<K,V>, tuple, record, and a struct that copies on assignment
  • Metatables become declarations: __index is an indexer or a property, __add is operator +, and __eq is what record gives you free
  • coroutine.wrap is yield return and coroutine.yield is await β€” the same machinery, with a scheduler you no longer write
  • The one real loss: nothing can send a value into a suspended yield return, where coroutine.resume always could
GDScriptPre-Alpha

Where 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.

  • 🚨 You own the loop in LΓ–VE; the engine owns it here β€” every node has its own _process(delta), and nothing iterates your entity list
  • 🚨 0, "" and empty collections are all falsy, so a zero-health check means the opposite of what it does in Lua
  • 🚨 7 / 2 is 3 β€” two integers divide as integers, which bites hardest when centering something
  • One table type splits into Array and Dictionary, and reading a missing key raises instead of giving you nil
  • Blocks come from indentation, not end; and there is no accidental global, because every name must be declared
  • Optional static types, a real match that destructures, and signals instead of polling a flag every frame
  • No pcall and no exceptions at all β€” you check before acting, or return an error code
RocPre-Alpha

Your one table becomes four types, and the compiler knows which you meant. A List, a Dict, a record and a tag union split the work a Lua table does alone β€” and there is no nil, no global scope to fall into, no metatable and no collector. Tail calls, //, % and byte strings all carry over unchanged.

  • The table splits four ways. A list of values is a List with a real length rather than a border; fixed keys are a record whose fields are checked at compile time; runtime keys are a Dict; and a kind field with a chain of comparisons becomes a tag union the compiler checks exhaustively.
  • There is no nil. Absence is a tag that names what is missing, so "absent" and "stored nothing" are different values β€” and a list can never grow a hole that makes # ambiguous.
  • No global scope. A missing local cannot be a bug, because there is no keyword to omit: every binding is local and every top-level definition is a compile-time constant.
  • pcall becomes the return type. A function that can fail returns Try(ok, err) and says so in its signature, with the error a typed tag rather than a string to parse.
  • map, filter, fold, sort, split and find, in the standard library β€” the six helpers every Lua project writes for itself β€” plus match, which destructures values rather than text.
  • Tail calls work the same way, and so do //, %, byte strings and the embedding instinct: a Roc platform is your C host, with the list of available effects checked by the compiler.
  • Be honest about the trade: no coroutines, no metatables, no operator overloading, no closure that holds mutable state, no load and no interpreter to embed. Roc is not a scripting language, and it is pre-1.0.
ForthPre-Alpha⚑ Works Offline⚑ Offline

The other language built to be embedded and stay out of the way. Lua and Forth are both famous for being small, and they mean quite different things by it: Lua gives you one data structure and a garbage collector in 250KB, while a Forth kernel fits in single-digit kilobytes by having no collector, no floats and no string library at all.

  • No local and no names: values wait on a stack, and DUP, SWAP and OVER put them where you need them
  • No table β€” an array is an address and a multiply, a record is a set of words that add fixed offsets, and neither knows its own length
  • CREATE … DOES> is a defining word: it generates the accessor words a metatable would have dispatched to
  • 🚨 No closures and no upvalues, so a counter's state lives in a VARIABLE β€” and there is exactly one of it
  • 🚨 No coroutines: one data stack and one return stack for the whole system, so a generator is turned inside out by hand
  • Zero is false and every other value true, the reverse of Lua β€” and AND is bitwise, so 1 2 AND is false
Drag cards to reorder Β· your order is saved locally