luna
v3.0.0 · MIT / Apache-2.0

A Lua runtime in pure Rust.

Five Lua dialects and MacroLua in a single binary. A zero-dependency interpreter core, a Cranelift-backed trace JIT, ahead-of-time native compilation, and a sandbox designed for embedding untrusted scripts.

5.1 – 5.5
dialects, plus MacroLua
0
dependencies in the core
514
fixtures byte-equal vs PUC
~33 KB
cold-start heap

One runtime, every Lua you need to embed.

luna implements the Lua language family from the ground up in safe Rust — no C, and no unsafe at the surface an embedder touches. Pick the dependency footprint you want and grow into the JIT and AOT tiers as your workload demands.

5.1 – 5.5 + MacroLua

Every mainline Lua dialect in one build, selected per-Vm at construction. A single process can host several dialects concurrently without interference. MacroLua adds compile-time @macro expansion over the 5.4 surface.

Zero-dependency core

luna-core pulls exactly one crate — itself. A cargo deny gate enforces it on every commit. That means a tiny audit surface, seconds-long builds, and a clean wasm32 target with no mmap RWX requirement.

Cranelift trace JIT

A trace-based compiler in the LuaJIT lineage. Hot loops record into typed IR, lower through Cranelift, and run as native machine code — plugged in behind a trait so the interpreter path never depends on the backend.

Ahead-of-time binaries

luna-aot turns a Lua source file into a self-contained native executable — bytecode, warmed traces, and a static runtime linked into one binary that runs with luna nowhere on the host.

Ergonomic embedding

An mlua-shaped Lua facade, typed native functions with automatic argument decode, table builders, userdata via a derive macro, Rust-side coroutines and debug hooks, and structured LuaErrors.

Built to contain scripts

The host owns the security boundary. Curated stdlib whitelisting, an instruction budget, an approximate memory cap, and bytecode loading off by default — every capability a script sees was opted in by Rust.

Byte-equal against reference Lua, on every push.

Compatibility is a measurement here, not a claim. A private corpus of 514 fixtures runs against stock PUC interpreters built from source — 5.1.5, 5.2.4, 5.3.6, 5.4.8 and 5.5.1 — and every one must match byte for byte, with zero skips, before a commit is green.

514 fixtures, five dialects

Compared on stdout, stderr and exit code against the reference binary for each dialect. CI additionally asserts the 5.5 interpreter is exactly 5.5.1, so the basis cannot drift with a runner image.

PUC's own tests

The upstream Lua test suite runs end-to-end across all five dialects with matching assert-count instrumentation — the same files PUC ships to validate its own releases.

ASAN, Miri, fuzz, soak

AddressSanitizer over the full official suite nightly, Miri for provenance and UB, seven fuzz targets weekly behind a gate that fails on any crash artifact, and long-running soak with a sub-1% RSS drift bound.

What each dialect can do.

Feature availability is driven by the capability predicates in src/version.rs. luna emits per-dialect bytecode matching PUC's compiler format, so PUC-compiled .luac files load directly.

Feature5.15.25.35.45.5MacroLua
Numeric
Integer subtype Int
// floor divide
Bitwise & | ~ << >>
Hex float 0x1p4
Syntax
goto / ::label::
Empty statement ;
Strings
\xXX / \z escapes
\u{XXXX} unicode escape
5.4+ attributes
local <const>
local <close>
5.5 exclusives
global keyword
Named vararg function f(...name)
MacroLua exclusives
@name(args) compile-time macros

Full matrix, stdlib coverage, and the C API surface live in the compatibility reference.

Choose your dependency surface.

luna ships as a Cargo workspace of seven published crates — three of them the ones you depend on directly. The interpreter core stands alone; the JIT and AOT tiers layer on top through a trait boundary, so removing or swapping the backend never touches the core API.

The interpreter

Lexer, parser, per-dialect compiler, dispatcher, runtime, stdlib, NaN-boxed values, an intrusive mark-sweep GC, the PUC pattern engine, and the JIT trait surface. Zero third-party crates.

The JIT + C ABI

The Cranelift-backed backend, a lua.h-compatible C ABI as cdylib / staticlib, the luna CLI, and the Lua embedding facade.

The AOT compiler

A build-time tool: Lua source → standalone native binary. Not a runtime dependency of what it produces.

The JIT pipeline

  1. Interpreter dispatcher

    Bump counter, check threshold.

  2. Trace recorder

    Replay opcodes into typed IR.

  3. Cranelift backend

    Lower IR, register-allocate, emit.

  4. Native machine code

    Runs until a side-exit.

Embed it in four lines.

Add the crate that matches the footprint you want, construct a Vm for the dialect you target, and evaluate.

Rust
// Cargo.toml → luna-jit = "3"

use luna_jit::{Vm, LuaVersion};

let mut vm = Vm::new(LuaVersion::Lua54);
let v = vm.eval("return 6 * 7")?;   // [Int(42)]

For an interpreter with no third-party dependencies at all, depend on luna-core instead. To produce a standalone native binary, use luna-aot as a build-time tool. The documentation covers the embedding API, the sandbox controls, and the dialect matrix in full.