v2.16.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 + MacroLua 0 deps in core ~33 KB cold-start heap lua.h-compatible C ABI
What luna is

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.

§ dialects

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.

§ core

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.

§ jit

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.

§ aot

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.

§ embed

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.

§ sandbox

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.


Dialect datasheet

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.


Architecture

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.

luna-core

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.

luna-jit

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.

luna-aot

The AOT compiler

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

The JIT pipeline

01 Interpreter dispatcher — bump counter, check threshold
↓ hot path detected
02 Trace recorder — replay opcodes into typed IR
↓ closed trace, across the trait boundary
03 Cranelift backend — lower IR, regalloc, emit
↓ mmap RWX + symbol relocation
04 Native machine code — runs until a side-exit

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.

Performance discipline

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.

33KB
cold-start peak heap · empty Vm
435
allocations · cold start
~4.5MiB
AOT binary · release, stripped
0
third-party deps · luna-core
Measured, reproducible baselines. Memory is profiled under dhat across five workloads (cold start, REPL idle, host-root churn, alloc + GC, userdata lifecycle); a >5% steady-state regression on any of them is a tracked alarm. Binary sizes are cargo publish --dry-run and stripped-release measurements. The reproduction recipes ship in the docs — nothing here is a number you have to take on faith.

The methodology, tuning knobs, and hot-path counters are documented in the performance reference.


Quick start

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.

Cargo.toml
# full interpreter + Cranelift JIT + C ABI
[dependencies]
luna-jit = "2"
main.rs — hello
use 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)
main.rs — sandbox
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)")?;