Risor vs Lua
Lua is the most established embedded scripting language, powering game engines, Redis, Nginx, and many other systems. But Go implementations have largely stopped active development, and Lua's conventions (1-based indexing, global-by-default variables) are unfamiliar to most developers today.
Risor is a newer alternative with TypeScript-like syntax, an opt-in sandbox, native JSON types, and built-in collection operations.
Syntax
Lua:
local users = {
{name = "Alice", role = "admin", active = true},
{name = "Bob", role = "viewer", active = false},
{name = "Carol", role = "admin", active = true},
}
local admins = {}
for _, u in ipairs(users) do
if u.active and u.role == "admin" then
table.insert(admins, u.name)
end
end
table.sort(admins)Risor:
let users = [
{name: "Alice", role: "admin", active: true},
{name: "Bob", role: "viewer", active: false},
{name: "Carol", role: "admin", active: true},
]
let admins = users
.filter(u => u.active && u.role == "admin")
.map(u => u.name)
|> sortedRisor also has try/catch as an expression and distinct list/map types that round-trip cleanly to JSON (Lua tables are ambiguous).
Comparison
| Property | Lua (via gopher-lua) | Risor |
|---|---|---|
| Sandbox model | Full access (opt-out) | Empty (opt-in) |
| Feature control | Remove globals | Host chooses which features to enable |
| Indexing | 1-based | 0-based |
| Variables | Global by default | Block-scoped (let) |
| JSON | Library-dependent, fragile | Native list/map types |
| Collections | Manual loops | .map(), .filter(), .reduce(), pipes |
| Syntax | Lua | TypeScript-like |
| Go ecosystem | gopher-lua (stable, not actively developed) | Actively developed |
Performance
Risor is faster than GopherLua across all three scenarios in our benchmarks, and the memory gap is dramatic.
For simple expressions, Risor evaluates in 15.2 µs versus GopherLua's 29.0 µs (1.9×). At scale the gap widens: Risor processes 10,000 records in 4.20 ms versus 7.62 ms (1.8×), and runs business logic in 273 µs versus 611 µs (2.2×). GopherLua's imperative loop model and heavy per-value boxing add up at scale.
Memory is where it gets stark. A compiled Risor instance uses 2.2 KB for a simple expression; GopherLua allocates a full Lua VM state at 179.8 KB. For business logic, Risor uses 7.6 KB versus GopherLua's 1,889 KB (249×). Running thousands of concurrent instances becomes impractical with GopherLua at those numbers.
| Scenario | GopherLua | Risor |
|---|---|---|
| Simple expression (warm) | 29.0 µs | 15.2 µs |
| Data processing (10k records) | 7.62 ms | 4.20 ms |
| Business logic (500 records) | 611 µs | 273 µs |
| Heap per instance (simple) | 179.8 KB | 2.2 KB |
| Heap per instance (business) | 1,889 KB | 7.6 KB |
When to Choose Lua
- Your users already know Lua (game dev, Redis scripting)
- You're embedding in C/C++ where Lua's FFI is unmatched
- You need general-purpose scripting and trust the code
When to Choose Risor
- Your team knows JavaScript/TypeScript, not Lua
- You want an opt-in sandbox with host-controlled features
- You're working with JSON-heavy data