The Luau Language
Luau is based on Lua 5.1, adds a gradual type system and several modern conveniences, selectively adopts features from later Lua versions, and removes the parts of standard Lua that would compromise sandboxing. If you know Lua, this page is the one that stops you getting caught out.
Back to the Luau scripting hub. For the exhaustive, version-pinned list of every difference, see luau_vs_lua51; to port real code pattern by pattern, the migration tutorials.
Heritage in one paragraph
Luau started from Lua 5.1 and evolved separately. It keeps backwards compatibility with 5.1 (including features later Lua versions deprecated, such as getfenv/setfenv), cherry-picks features from Lua 5.2 through 5.5 where they earn their keep, adds a substantial type system that standard Lua does not have, and omits anything that breaks the sandbox or that the Luau authors judged not worth the implementation cost. AntOS compiles its VM from the current official Luau source, so the behaviour described here is upstream Luau, not a fork.
Things Luau adds that Lua doesn't have
These are genuine Luau extensions — you won't find them in any standard Lua.
Gradual type annotations
Luau has an optional, gradual type system. You can annotate variables, parameters, and return types; untyped code keeps working unchanged.
local count: number = 0
local name: string = "ant64"
local function greet(who: string): string
return `Hello, {who}`
end
type Point = { x: number, y: number }
local p: Point = { x = 1, y = 2 }
Types are checked statically by the Luau analyser and then erased — they have no runtime cost and no runtime presence. They are documentation and a correctness aid, not a runtime mechanism.
continue
Luau adds continue inside loops, which Lua never had (Lua's answer was goto, which Luau does not have — see below).
for _, item in items do
if item.skip then continue end
process(item)
end
Compound assignment
x += 1
y -= step
total *= 2
acc ..= chunk -- string append
n //= 2 -- floor-divide assign
The full set: +=, -=, *=, /=, //=, %=, ^=, ..=.
String interpolation
Backtick strings interpolate expressions inside { }:
local who, n = "world", 3
print(`Hello {who}, attempt {n} of {n + 2}`)
Generalized iteration
for ... in can iterate a table directly — no pairs/ipairs wrapper needed — and any object can make itself iterable with the __iter metamethod.
for key, value in myTable do
-- works directly, no pairs()
end
if ... then ... else expressions
if can be an expression that returns a value, not only a statement:
local label = if score >= 50 then "pass" else "fail"
const variables
A context-sensitive const keyword marks a binding immutable:
const MAX = 128
(Luau deliberately did not adopt Lua 5.4's <const> attribute syntax; this is the Luau form.)
Richer numeric literals
Hexadecimal (0xFF), binary (0b1010), and underscore digit separators (1_000_000) are all accepted.
Things Luau removes or changes — the traps
These are where Lua habits bite. Read them carefully.
No standard io library
This is the big one for an OS scripting environment. Luau's standard library does not include the io library at all, and the os, package, and debug libraries are absent or cut down — all removed for sandboxing, because a sandboxed VM must not hand scripts unmediated access to the host's files and processes. AntOS supplies its own io, os, and fs libraries in their place, tailored to DBFS and the CM5 Linux environment. See AntOS libraries. Do not assume standard-Lua io.open/io.write semantics — they are not what AntOS provides.
No goto
Lua 5.2 added goto/labels; Luau deliberately omits them. Use continue, break, early return, or restructured control flow instead.
One number type, no 64-bit integers
Luau has a single number type (an IEEE double), exactly like Lua 5.1. It does not adopt Lua 5.3's separate 64-bit integer type. Integers are exact up to 2^53. This keeps the language simple and fast, but if you are porting code that relies on 64-bit integer wraparound, it won't behave the same.
A distinct 🚧
integertype — true 64-bit, but a separate type with no arithmetic operators (you callinteger.add,integer.lt, … instead) — is landing across the 0.72x series. It is not Lua 5.3's integer subtype and does not changenumber. See the complete reference.
Bitwise work goes through bit32, not operators
Because there are no first-class integers, Luau does not have the Lua 5.3 bitwise operators (&, |, ~, <<, >>). Use the bit32 library instead — it operates on 32-bit values and includes rotates and arithmetic shift, which the operators don't express cleanly anyway. bit32 calls compile to VM builtins, so they are fast.
No bytecode loading
loadstring of bytecode and string.dump are removed — exposing bytecode is a sandboxing hazard. loadfile and dofile are gone too (no direct file access). Loading source strings may be available depending on how the host configures the VM, but never rely on loading precompiled bytecode.
No tail-call optimisation
Luau does not implement proper tail calls. This simplifies the implementation and makes stack traces predictable, but it means deeply recursive tail-style code can overflow the stack where Lua would not. Prefer iteration for unbounded recursion.
getfenv / setfenv still present, but avoid them
Luau keeps getfenv/setfenv for 5.1 compatibility even though later Lua removed them. They defeat some of Luau's optimisations and are best avoided in new code.
Minor behavioural differences
A few semantics differ from Lua 5.x by design: __eq is called even when operands are raw-equal (this makes NaN handling consistent); table-literal assignment follows program order in mixed tables; and os.time returns a UTC timestamp when called with a table. These rarely matter day to day but are worth knowing.
Quick reference — Lua habit vs Luau reality
| If you reach for… | In Luau, instead… |
|---|---|
goto continue pattern |
use continue directly |
a & b, a << n |
bit32.band(a, b), bit32.lshift(a, n) |
io.open / io.write |
AntOS io / fs / terminal |
| 64-bit integer maths | number (exact to 2^53) or bit32 for 32-bit |
| deep tail recursion | an explicit loop |
for k,v in pairs(t) |
for k,v in t (generalized iteration) |
string building with .. in a loop |
..=, or collect into a table and table.concat |
local x <const> = v |
const x = v |
Where to go next
- The functions that ship with the VM → Standard built-in libraries
- The functions AntOS adds → AntOS libraries
- Every difference, exhaustively → Lua 5.1 → Luau complete reference
- Hands-on porting, pattern by pattern → Migration tutorials
- Back to the Luau scripting hub