Skip to content

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:

local total = 60
local label = if total > 50 then "large" else "small"
print(label) -- large

Standard Lua needs a statement and separate assignments:

local total = 60
local label
if total > 50 then
    label = "large"
else
    label = "small"
end

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 {}:

local name = "Ada"
local count = 3
print(`Hello, {name}: {count + 1} tasks`)

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:

local names = {"Ada", "Grace"}
for index, name in names do
    print(index, name)
end

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:

[dependencies]
mlua = { version = "=0.12.2", features = ["luau"] }

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:

dotnet add package NuLua --version 0.1.0
dotnet add package NuLua.Luau --version 0.1.0

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.