Which Lua? The language, its runtimes, and its surroundings¶
First version: October 18, 2026.
Someone says their application uses Lua. That tells you something about the syntax, tables, closures, and the likely presence of an embedded interpreter. It leaves several practical questions unanswered. Which version? Which implementation? Who provides the libraries? Can scripts load native modules? Does the editor understand the host's objects?
The original implementation continues to evolve under a small team's control. LuaJIT provides another runtime with its own compatibility commitments. Roblox developed Luau into a language with static analysis and different syntax. Other projects add types or macros while generating ordinary Lua.
Applications embedding Lua often determine which version, libraries, and tools their users work with.
The original Lua has its own way of working¶
Lua's development process can surprise someone accustomed to finding an issue tracker, opening a pull request, and waiting for review.
There is an official GitHub mirror, but it is updated irregularly and explicitly asks people not to send pull requests. Bug reports and suggestions belong on the Lua mailing list. The FAQ describes Lua as “open-source software but it is not openly developed.” It also explains that the team welcomes tested solutions as discussion material, then writes its own implementation when it decides to address a problem. Third-party code is not incorporated verbatim.
The source is available and discussion is public; ownership of the implementation and its design remains concentrated. Looking only at GitHub activity gives an incomplete picture of maintenance, and a patch arriving on the mailing list does not enter a conventional merge queue.
Mainline Lua has moved on¶
The current release at the time of writing is Lua 5.5.1, released on August 3, 2026. Lua 5.5 arrived in December 2025 with global variable declarations, named vararg tables, more compact arrays, and incremental major garbage collection. Earlier milestones include Lua 5.3's integers and bitwise operators, and Lua 5.4's const and to-be-closed variables. Official version history
Lua does not use semantic versioning: 5.4 to 5.5 is a language-version change, with possible API and source incompatibilities and no ABI compatibility. Native modules and applications embedding Lua need recompilation. Patch releases within a version have a different compatibility promise. Precompiled bytecode is also tied to a version. Lua's numbering scheme
An application may have years of scripts, plugins, and native bindings depending on its runtime's existing behavior. Upgrading the interpreter can require changes throughout that code.
This makes “old Lua” an ambiguous description. It can mean an unsupported upstream release, or the language baseline of an actively maintained runtime.
LuaJIT: performance with a lasting baseline¶
LuaJIT implements Lua 5.1 compatibility and adds facilities such as its foreign function interface, bit operations, and selected features from later Lua versions. It is a separate implementation with a just-in-time compiler, rather than a switch that enables compilation in the current mainline Lua interpreter.
With its FFI, scripts can declare C types and call C functions without writing a conventional Lua C binding for every operation. Such code needs LuaJIT or an implementation of the same FFI API. LuaJIT extensions
LuaJIT's release model is another source of confusion. The project currently describes itself as actively maintained, recommends the 2.1 branch for production, and uses rolling releases whose version includes a commit timestamp. Its status page identifies the repository hosted by luajit.org as the authoritative origin. A familiar old version number is insufficient evidence that development has stopped. LuaJIT status
A runtime can receive fixes and platform work while continuing to implement an older language version.
Luau grew out of Roblox's requirements¶
Roblox began using Lua 5.1 around 2006, accumulating changes for its sandbox and libraries. Its own history dates the deliberate effort to build Luau to 2019. By November that year, Roblox was already reporting that the new implementation had become the default for most platforms and configurations. Luau went open source on November 3, 2021. Why Luau?, origins and evolution, November 2019 recap, open-source announcement
“Forked in 2019” is useful shorthand, but it compresses years of earlier adaptation into one event. It can also suggest more shared implementation than remains: the Luau team says a large part of the codebase was written from scratch. Implementation background
Roblox's codebases had grown, performance requirements had increased, and the lack of type checking had become painful. Upgrading upstream Lua would break features already in widespread use. LuaJIT did not meet its portability and ease-of-change requirements or solve the analysis problem. Why Luau?
The changes accumulated over time; the following describes today's language, not a list of features delivered at its initial launch.
If expressions¶
An if expression produces a value:
Standard Lua needs a statement and separate assignments:
Luau requires else and omits end in the expression form. It also handles
false values correctly, unlike the condition and value or fallback idiom:
local disabled = true
local enabled = if disabled then false else true -- false
local wrong = disabled and false or true -- true
Interpolation, assignment, and continue¶
Backtick strings embed expressions in {}:
Compound assignments shorten updates. continue skips the remaining loop body:
local total = 0
for _, value in ipairs({10, -5, 20}) do
if value < 0 then
continue
end
total += value
end
print(total) -- 30
These examples use Luau syntax; they do not run unchanged in standard Lua. Luau syntax reference
Iterating directly over tables¶
Luau accepts a table directly after in:
For a dense array, this visits elements in index order. Tables can also contain
other keys; their order is unspecified after the array portion. Custom objects
can supply an __iter metamethod. Existing pairs and ipairs loops still
work. Generalized iteration RFC
Types for function boundaries and data¶
Annotations describe parameters, return values, and table shapes. --!strict
enables stricter analysis for the file:
--!strict
type Task = {
title: string,
done: boolean,
}
local function describe(task: Task): string
return if task.done then "complete" else task.title
end
print(describe({title = "Write article", done = false}))
-- describe({title = "Write article", done = "no"}) -- type error
The checker infers many types without annotations. I would start with strict mode and annotate function boundaries and shared data structures. Those annotations document what callers should supply and let the checker catch mistakes before execution. They do not insert runtime argument checks. Luau types
Table helpers¶
Luau also extends the standard library:
local defaults = {retries = 3, timeout = 10}
local options = table.clone(defaults)
table.freeze(defaults)
options.retries = 5
local values = table.create(4, 0) -- four zeroes
print(table.find({"red", "green", "blue"}, "green")) -- 2
table.freeze prevents changes to that table; nested tables remain mutable.
table.clone makes a shallow, unfrozen copy, so nested tables remain shared.
table.create preallocates an array, optionally filling it, and table.find
returns the index of a matching array element or nil.
Luau standard library
A different implementation and sandbox¶
Luau's performance work spans its compiler, bytecode interpreter, specialized built-ins, and native code generation. Performance comparisons need to specify the execution mode, platform, and workload. How Luau is made fast
Luau preserves a Lua 5.1 baseline while deliberately omitting facilities for sandboxing, including direct file loading and exposed bytecode. It does not implement the full sequence of later mainline Lua features. Its compatibility matrix lists the supported features and omissions.
Luau accepts contributions through its public implementation repository and a public RFC process for language evolution.
Adding a language without replacing the runtime¶
Some projects change what developers write while keeping Lua as the execution target.
Teal is a typed dialect whose compiler generates Lua. Its documented runtime support includes Lua 5.1 through 5.4 and LuaJIT. Type checking happens before execution, and the generated program runs on an existing Lua implementation.
Fennel is a Lisp that compiles to Lua, with compile-time macros and access to Lua libraries. Developers can ship generated Lua without a Fennel runtime dependency.
| Project or approach | What changes? | What executes the program? |
|---|---|---|
| Mainline Lua | The language and its reference implementation evolve together | The selected Lua version |
| LuaJIT | Implementation, performance behavior, and extensions | LuaJIT |
| Luau | Language, analysis tools, implementation, and sandbox facilities | Luau |
| Teal | Typed source compiled into Lua | A supported Lua runtime |
| Fennel | Lisp syntax and macros compiled into Lua | A compatible Lua runtime |
Tooling can add types while the source stays Lua¶
Lua Language Server provides completion, diagnostics, type analysis, and annotations for parameters, returns, and classes. Definition files can describe libraries and host APIs.
Lua Language Server reads type annotations from comments, so the source remains valid Lua. Teal and Luau put annotations in the language syntax and require their own compiler or analysis tools.
LuaRocks supplies a package manager and registry for Lua modules. A module may depend on a particular Lua version, a native library, or facilities supplied by one host. Finding a package and being able to use it inside an embedded application are separate steps.
Embedding in Rust and .NET¶
Embedding means linking a runtime into your application, registering host functions, and running scripts inside that process. A wrapper handles value conversion and VM lifetimes; you still design the API scripts will use.
| Host | Library | Runtime | Deployment |
|---|---|---|---|
| Rust | mlua |
Luau, mainline Lua, or LuaJIT, selected by features | Native runtime; Luau builds from bundled sources |
| .NET | NuLua | Luau, mainline Lua, or LuaJIT, selected by package | Native binaries; currently desktop platforms |
| .NET | NLua | Mainline Lua through KeraLua | Managed wrapper plus native Lua |
| .NET | MoonSharp | Its own Lua 5.2-oriented interpreter | Entirely managed C# |
Rust: mlua with the Luau feature¶
mlua supports Luau directly. Its luau
feature builds the native runtime from bundled sources, so a separate system
Luau installation is unnecessary. Optional features include async, serde,
and luau-jit for native code generation.
For this example, use:
The host registers a Rust function, and the script calls it:
use mlua::{Lua, Result};
fn main() -> Result<()> {
let lua = Lua::new();
let double = lua.create_function(|_, value: f64| Ok(value * 2.0))?;
lua.globals().set("double", double)?;
lua.sandbox(true)?;
let result: String = lua.load(r#"
local amount = double(21)
return if amount == 42 then "ok" else "unexpected"
"#).eval()?;
println!("{result}"); // ok
Ok(())
}
Lua::create_function converts the
argument into the declared Rust type and returns conversion errors through
mlua::Result. eval similarly converts the script's result into String.
Host objects can implement UserData to expose selected methods and fields.
For asynchronous host operations, enable async and use the asynchronous
execution APIs. The integration uses Lua coroutines; the application supplies
an executor. This does not make every script operation asynchronous.
mlua async support
.NET: NuLua for Luau¶
NuLua has a NuLua.Luau backend, host
callbacks, and async support. It is currently a preview supporting Windows,
macOS, and Linux on x64 and ARM64. Mobile and web support are listed as planned.
The older luau-dotnet project is archived and directs users to NuLua. Avoid starting from its old package names and examples.
Install the packages:
The equivalent C# host callback looks like this:
using NuLua;
using NuLua.Luau;
using var state = LuauState.Create();
state.OpenLibraries();
state.RegisterFunction("double", (lua, args) =>
{
lua.Push(args[0].Read<double>() * 2);
return 1;
});
var results = state.DoString("""
local amount = double(21)
return if amount == 42 then "ok" else "unexpected"
""");
Console.WriteLine(results[0].Read<string>()); // ok
Push supplies a return value; the callback returns how many values it pushed.
Dispose the state when finished. NuLua also provides Luau sandbox APIs and
custom module loaders. NuLua API examples
Both examples were compiled and run on macOS ARM64: mlua 0.12.2 and NuLua
0.1.0, using .NET 10. Both printed ok. This verifies the basic callback and
Luau execution path on that platform.
.NET alternatives when Luau is optional¶
NLua bridges native Lua to .NET through KeraLua. It exposes .NET objects and methods to scripts and suits applications that want mainline Lua with CLR interoperability. It is not a Luau backend; the Luau examples earlier in this article will not run unchanged.
MoonSharp implements its interpreter entirely in C#. It targets Lua 5.2 compatibility with documented differences, offers CLR object integration, and avoids shipping a native Lua library. Its project documents AOT and Unity IL2CPP support. It also does not implement Luau's syntax or type checker.
For a Luau application, I would prototype with mlua in Rust or NuLua in .NET. NuLua's preview status makes testing deployment on the intended platforms an early task. If managed-only deployment is a requirement and Lua 5.2 is acceptable, MoonSharp is a separate option.
What the embedding library leaves to the host¶
Luau's sandbox documentation covers restricted libraries, read-only built-ins, separate script environments, and interruption of running code. A sandboxed VM still has access to whatever host callbacks you register. A callback that reads arbitrary files gives scripts that ability.
Decide how modules are found, how much memory scripts may allocate, and how long they may run. Interrupting script execution also needs a policy for host callbacks that block or perform expensive work. These examples demonstrate integration; neither implements a complete resource-limited script service.
Runtime execution and static analysis are separate integrations. Loading a script with either wrapper does not automatically run Luau's type checker. To check calls into your host API, supply matching Luau type declarations and run analysis in the editor or build pipeline. Keep runtime argument validation in the host callbacks as well.
The host is part of the programming environment¶
In a standalone program, you can select the interpreter, install modules, and control the filesystem. Inside another application, those decisions may already belong to the application author.
The host chooses which functions to expose, how scripts enter the system, what objects they manipulate, and which operations are permitted. That environment becomes part of what developers mean when they say “Lua,” even when the functions they use are absent from Lua's standard library.
Luau is an embeddable language with its own compiler and VM. Roblox supplies an application platform around it. Another Luau embedder does not automatically acquire Roblox's services, objects, or execution model. Luau repository
Before choosing a library or sharing code between applications, check the language version, runtime, available host APIs, and module-loading rules. A pure Lua module may travel easily; code using LuaJIT's FFI, Roblox services, or a particular native-module ABI needs the corresponding environment.