Lua Differences

Every way Ember departs from Lua 5.4, with an id, a reason, and a kind.

This page is a template and a discipline, not a finished document — you write it as you go, one line at a time, and two tests keep it honest:

  • A corpus case may skip the lua comparison only by naming an id that exists here (Lab 26).
  • Every entry with a global name produces a help: line in the diagnostic renderer (Lab 24).

The kinds matter. A divergence is a decision; a limitation is unfinished work. Conflating them is how a limitations document becomes an apology and a divergence list becomes an excuse.

KindMeaning
DIVERGENCEWe deliberately do something else, for a stated reason
LIMITATIONNot implemented. Belongs in docs/limitations.md too
UNSPECIFIEDLua does not define it; we do. Ours is a narrowing, not a conflict

Semantics

idKindEmberLua 5.4Reason
D-coerce-arithDIVERGENCE"10" + 5 is a type error15Coercion from string is a parse that can fail, turning a type error into a wrong answer far from its cause. Coercion to string in .. is kept, because it is total
D-string-orderDIVERGENCEByte-wise comparisonstrcoll — locale-dependenttable.sort on strings must not differ between machines. ADR-009
D-pairs-orderDIVERGENCEInsertion order, guaranteedunspecifiedReproducible output and non-flaky tests. ADR-008. A promise we cannot withdraw
U-length-holesUNSPECIFIED#t is the array part after trailing nils are trimmed"any border"Deterministic. A narrowing of Lua's licence, not a conflict
D-eval-orderDIVERGENCEOperands evaluate left to right, alwaysunspecified for most expressionsDeterminism, and it makes the two backends comparable
D-nan-formatDIVERGENCEtostring(0/0) is nan on every platformplatform-dependent spellingDeterminism
D-float-formatDIVERGENCERust's shortest round-tripping form%.14gChosen deliberately; golden tests match ours

Types and Values

idKindEmberLua 5.4Reason
D-strings-bytesDIVERGENCEByte strings; no Unicode-aware operationsbyte strings + a utf8 librarySame representation; the library is a Lab 16 challenge
L-utf8LIMITATIONno utf8.*presentNot implemented
D-escape-bytesDIVERGENCE\xNN above 0x7F becomes U+0080..U+00FFa raw byteDocumented consequence of the string type; revisit if it bites

Missing Language Features

idKindReason
L-coroutinesLIMITATIONStructural: a second VM state. Capstone project 4
L-gotoLIMITATIONThe compiler has no way to close upvalues on an arbitrary jump; Lua needs a page of machinery for it
L-repeatLIMITATIONrepeat…until — its scope rule (the condition sees the body's locals) is unusual; a Lab 6 challenge
L-bitwiseLIMITATION& | ~ << >> — integer-only semantics, deferred
L-tailcallLIMITATIONNo proper tail calls. A tail-recursive state machine will hit the depth limit. Easy in the VM, hard in the tree walker
L-closeLIMITATIONlocal x <close> needs to-be-closed machinery in the compiler

Metatables

idKindReason
D-no-gc-metaDIVERGENCENo __gc: runs at a nondeterministic time and can resurrect its object
D-no-weakDIVERGENCENo __mode: weak tables need a second marking phase
D-no-metatable-fieldDIVERGENCENo __metatable protection: sandboxing via capabilities is stronger than via metatables
D-le-fallbackDIVERGENCEFollows 5.4 (no not (b < a) fallback), not 5.3
D-chain-limitDIVERGENCE__index chain limit is 100; Lua's MAXTAGLOOP is 2000

Standard Library

idKindAbsentReason
D-no-ioDIVERGENCEio.*Filesystem access. A host registers narrow functions instead
D-no-os-execDIVERGENCEos.execute, os.remove, os.rename, os.exitProcess and filesystem control; os.exit can kill the host
D-no-os-envDIVERGENCEos.getenvEnvironment variables routinely hold credentials
D-clock-capabilityDIVERGENCEos.time, os.clock, os.date behind CLOCKNondeterministic
D-random-capabilityDIVERGENCEmath.random behind RANDOM with a host seedNondeterministic
D-no-loadDIVERGENCEload, loadstring, dofile, loadfileRun-time compilation defeats static policy review; loadfile is filesystem access
D-no-dumpDIVERGENCEstring.dumpEmits bytecode; nobody in this profile needs it
D-no-debugDIVERGENCEdebug.*Defeats every other sandbox control. If debug is present, nothing else in this list matters
D-require-capabilityDIVERGENCErequire only with a host resolverADR-010
D-pcall-limitsDIVERGENCEpcall does not catch Limit or Host errorsCatching Limit would defeat every CPU control in one line
D-sort-stabilityDIVERGENCE(decide and record)Lua's table.sort is not stable. If yours is, that is a divergence

Implementation-Visible

Not language differences, but things a Lua programmer will notice:

idKindNote
D-no-light-userdataDIVERGENCELight userdata is a raw pointer in a safe API
D-engine-not-sendDIVERGENCEEngine is !Send + !Sync; a tokio task cannot hold one across an .await
D-stw-gcDIVERGENCEStop-the-world collector; Lua's is incremental. See your measured pause distribution
D-closure-pseudoDIVERGENCEUpvalue capture descriptors live in the Proto, not as pseudo-instructions after CLOSURE

The Rules for Adding an Entry

  1. An id, and it is referenced from a corpus case or a test. An entry nothing exercises is an entry nobody can trust.
  2. A reason, not a restatement. "We do it differently" is not a reason. "Coercion from string is a parse that can fail" is.
  3. The right kind. If the honest reason is "we ran out of time", it is a LIMITATION and it goes in docs/limitations.md too. Calling unfinished work a divergence is the one dishonest move available on this page.
  4. A help: line where it applies. A user who types os.time() should be told what happened and what to do, not just that os is nil.

Self-check

  1. Why are DIVERGENCE and LIMITATION different kinds? Which one is the dishonest move?
  2. Which two tests keep this page from rotting?
  3. Why is coercion to string kept while coercion from string is rejected?
  4. Which single omitted library would, if present, make every other omission on this page pointless?
  5. Pick three entries and give the reason without looking.

Next: Rust Patterns for Runtime Authors.