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 🚧 integer type — true 64-bit, but a separate type with no arithmetic operators (you call integer.add, integer.lt, … instead) — is landing across the 0.72x series. It is not Lua 5.3's integer subtype and does not change number. 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

Important: The Ant64 family of home computers are at early design/prototype stage, everything you see here is subject to change.