RC RANDOM CHAOS

LuaJIT 3.0's named varargs can skip the table allocation

LuaJIT issue #1475 is the umbrella proposal for LuaJIT 3.0's syntax extensions: bit operators, ternary, safe navigation, const, continue and more.

· 4 min read
LuaJIT 3.0's named varargs can skip the table allocation

Issue #1475 in the LuaJIT repository is an umbrella document for every syntax extension planned for LuaJIT 3.0, and the list runs long: C-style bit operators, the customary spellings !, &&, || and !=, a ternary conditional ?:, a safe navigation operator ?., a nil-coalescing operator ??, compound assignment (+=, -=, *= and the rest), floor division //, a continue statement, a const declaration, arrow-style short functions, named vararg parameters, underscores in number literals, semicolons allowed anywhere, and a C-preprocessor-style line directive. Part of the work is consolidation, since LuaJIT’s existing extensions are documented in scattered places, and part is genuinely new surface.

The author sets a bar for what gets in: it has to improve day-to-day ergonomics, be proven in another language or Lua dialect, introduce no syntactic ambiguity, break no backwards compatibility, and not make life hard for formatters and language servers. There is an explicit line about not wanting the syntactic weight of Perl, Ruby, C++ or Rust.

The semantics are where the detail matters.

Bit operators

~ is xor, because ^ is already exponentiation in Lua. Right shift splits into >> for logical and ~>> for arithmetic; >>> was rejected because JavaScript uses it for a logical shift and the overlap would mislead. The operators work on plain Lua numbers, FFI cdata integers and integer vectors, and fall through to a metamethod on anything else. The bit.* library stays. The compatibility catch is worth flagging: these bitwise semantics differ from every Lua 5.2+ minor version, and those versions are mutually incompatible as well, so for code that has to run across Lua and LuaJIT the issue points you at the Lua BitOp module instead of the operators.

The null-safety trio

The ternary a ? b : c short-circuits and is right-associative, so nested conditions read left to right. It has one sharp edge: b cannot directly hold a method call, because obj:method() creates a parse ambiguity, so you wrap it as cond ? (obj:method()) : default. The operator earns its place by fixing the old a and b or c trap, where a truthy a still returns c when b is nil or false. true and false or 42 yields 42; true ? false : 42 yields the false you meant.

Safe navigation ?. checks the left side for nil before indexing or calling, and in LuaJIT only nil values and NULL cdata pointers compare equal to nil. When the left side is nil, the rest of the chain is suppressed: no indexing, no call, no metamethod lookup, and in an indexed assignment the right-hand expression is not evaluated either, which matters when it has side effects. A chain like a?.b?.[c]?.d?.:e?.() performs five independent checks.

nil-coalescing a ?? b returns a unless it is nil, in which case it returns b, and a is evaluated once. It replaces a or b, which also hands back b when a is false, and which treats a NULL cdata pointer as truthy.

Compound assignment

obj.ctx.arr[idx].count += 10 resolves the target path once rather than twice, so x.y.z.field += b behaves as local tmp = x.y.z; tmp.field = tmp.field + b. It invokes __index and __newindex where the long form would, and when the operation itself needs a metamethod it passes true as a third argument, as in __add(a, b, true), which lets the metamethod mutate in place and return the first operand. There is no ++ or --, since -- collides with comment syntax, and ^= was omitted on purpose because in most languages that spelling means xor-assign.

const, continue, short functions

const gives a block-scoped binding that cannot be reassigned, though the contents of a const table remain mutable. The proposal is blunt that this is about catching mistakes rather than speed: “a modern compiler can figure out ‘constness’ of a variable by itself and is helped only very little by a constant declaration.” continue is the jump-to-hidden-label at the end of a loop that people have long written by hand with goto. Both are soft keywords, so existing code that uses const or continue as a variable name still parses. Short functions give you |a, b| -> a + b and, for a single parameter, x -> x + 1; the issue cautions that Lua is imperative and LuaJIT is not tuned for heavy functional style, so leaning hard on lambdas will disappoint.

Named varargs, the one with a speed story

function f(a, b, ...va) binds va to a const table whose va.n holds the argument count. Built by hand with select("#", ...), that table is a real allocation; with the named form the VM can elide the allocation when the only uses are read-only indexing, which makes it both tidier and faster than plain .... Use va.n, not #va: the length operator reports the wrong count when nil arguments are passed, so (|...va| -> #va)(nil, nil) is 0 while va.n is 2.

What to watch

Underscores in number literals (1_234_567_890) are accepted by the source parser in any position after the first digit, across every base and suffix. tonumber() and implicit string-to-number coercion do not accept them, so a separator that is valid in a literal will fail the moment the same text reaches you as a string through config or user input. The line directive (# 123 "input.file") and semicolons-anywhere exist for code generators, so generated Lua can carry its original file and line into error messages.

Share

Keep Reading

Latest on the Wire

Full wire →

New signal daily · RSS

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.