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.
One runtime, every Lua you need to embed.
luna implements the Lua language family from the ground up in safe Rust — no C, 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 arg 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.
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.
| Feature | 5.1 | 5.2 | 5.3 | 5.4 | 5.5 | MacroLua |
|---|---|---|---|---|---|---|
| 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 five publishable crates. 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
Source classification
Every file sits in one of three tiers, each with its own change discipline and review depth:
- Stone — business-agnostic foundations (pattern engine, heap, value layout). Fuzzed, benched, semver-strict.
- Steel — Lua-domain primitives (compiler, dispatcher, JIT backend). Cross-validated against PUC per dialect.
- Cement — host glue (CLI, C ABI, stdlib bindings, AOT). Free to evolve as embedder needs change.
Honest numbers, or none.
luna deliberately does not ship a cherry-picked headline ratio. A single microbench cell that wins is a marketing artifact; the outlier that loses is the signal worth chasing. What we publish are reproducible baselines and the methodology that produced them.
The methodology, tuning knobs, and hot-path counters are documented in the performance reference.
Embed it in three lines.
Add a crate, open the libraries you want, evaluate. Grow into the sandbox, native functions, and userdata when you need them.
# full interpreter + Cranelift JIT + C ABI
[dependencies]
luna-jit = "2"# minimum surface — pure interpreter, zero deps, wasm-friendly
[dependencies]
luna-core = "2"cargo install luna-jit # the `luna` REPL + script runner
luna -e "print('hello, world')"
luna path/to/script.luause luna_jit::vm::Vm;
use luna_jit::version::LuaVersion;
let mut vm = Vm::new(LuaVersion::Lua55);
let out = vm.eval("return 1 + 2")?;
// out[0] == Value::Int(3)use luna_jit::Lua;
use luna_jit::version::LuaVersion;
let mut lua = Lua::sandbox(LuaVersion::Lua54)
.open_base().open_math().open_string()
.with_instr_budget(1_000_000)
.with_memory_cap(8 * 1024 * 1024)
.build();
let add = lua.create_function(|a: i64, b: i64| a + b);
lua.set_global("add", add)?;
let r: i64 = lua.eval("return add(40, 2)")?;