Back
· 5 min read
Balaur Script
Balaur Script is the engine's scripting language: typed .bs files with Rust's syntax, compiled to WebAssembly. Measured beside Rune, Lua, Luau and Rust.
- Dragos Balaur
Balaur Script is the engine's scripting language: .bs files with static types and Rust's syntax, compiled to WebAssembly. Desktop, Android and the web run the same module, and iOS runs it translated to C.

The language
pub struct Spinner {
pub speed: float = 2.0,
pub clockwise: bool = true,
node: Node,
angle: float,
direction: float,
}
impl Spinner {
fn init() {
self.direction = if self.clockwise { 1.0 } else { -1.0 };
}
fn update(dt: float) {
if input::action_just_pressed("reverse") {
self.direction = -self.direction;
}
self.angle += dt * self.speed * self.direction;
self.node.transform.rotation_euler = vec3(0.0, self.angle, 0.0);
}
}
- A
pubfield with a default is a property the inspector shows. - A
structis a value kept in WebAssembly locals, so aVec3allocates nothing. Aclassis one object every holder shares. T?is a value ornil, andanyholds free-form data such as an HTTP reply.- Strings, lists and classes are reference counted. No collector runs.
await task::seconds(0.5)parks anasync fnuntil the engine wakes it.
Beside Rune, Lua and Rust
| Balaur Script | Rune 0.14 | Lua 5.4, Luau | Rust | |
|---|---|---|---|---|
| Types | static | dynamic | dynamic; Luau adds optional annotations | static |
| Syntax | Rust's, without lifetimes or a borrow checker | Rust's | local, end, tables from 1 |
Rust |
| Runs as | WebAssembly: a JIT, an interpreter, the browser's engine, or C | bytecode on Rune's VM | bytecode on Lua's VM; LuaJIT and Luau --codegen emit machine code |
machine code |
| Memory | reference counts | reference counts | a garbage collector | ownership |
Closures, match, enums |
no | yes | closures | yes |
| Async | async fn, await f() |
async fn, f().await |
coroutines | async fn, f().await |
fn fib(n: int) -> int { if n < 2 { n } else { fib(n - 1) + fib(n - 2) } }
fn fib(n) { if n < 2 { n } else { fib(n - 1) + fib(n - 2) } }
local function fib(n)
if n < 2 then return n end
return fib(n - 1) + fib(n - 2)
end
fn fib(n: i64) -> i64 { if n < 2 { n } else { fib(n - 1) + fib(n - 2) } }
Speed
| particles | × Rust | fib(25) | × Rust | |
|---|---|---|---|---|
| Rust | 1.32 ms | 1.0 | 0.23 ms | 1.0 |
| Balaur Script → C | 1.50 ms | 1.1 | 0.29 ms | 1.3 |
| LuaJIT 2.1 | 1.95 ms | 1.5 | 0.43 ms | 1.9 |
| Balaur Script, wasmtime | 1.98 ms | 1.5 | 0.36 ms | 1.5 |
Luau 0.667, --codegen |
15.7 ms | 11.9 | 2.02 ms | 8.8 |
| Balaur Script, wasmi | 20.6 ms | 15.6 | 2.50 ms | 10.9 |
| Lua 5.4 | 44.3 ms | 33.6 | 4.46 ms | 19.4 |
| Luau 0.667 | 56.5 ms | 42.9 | 4.21 ms | 18.3 |
| Rune 0.14 | 356 ms | 271 | 15.8 ms | 68.9 |
The particle step, in Balaur Script and in Rust:
for i in 0..n {
let x = xs[i] + vxs[i] * 0.016;
if x < 0.0 || x > 100.0 { vxs[i] = -vxs[i]; }
xs[i] = x;
}
for i in 0..n {
let x = xs[i] + vxs[i] * 0.016;
if !(0.0..=100.0).contains(&x) { vxs[i] = -vxs[i]; }
xs[i] = x;
}
- Apple M1, best of 80 runs each, with every runtime's calls interleaved. All nine return the same sum, bit for bit.
- Balaur Script → C is what an export ships where clang and lld are found: the module translated to C and built at
-O2. - The host makes every NaN it reads out of a module the canonical one, so its bits match on every machine. No engine rewrites NaNs per operation, which would put wasmtime's particles at 2.3×.
- The rest of wasmtime's gap to C is Cranelift, which optimises less than clang.
- An engine call costs 54 ns as C, 92 on wasmtime and 120 on wasmi, against Rune's 141.
Where it runs
- The editor and an Android or desktop game without the library run scripts on wasmtime. A cached module loads in 0.84 ms.
- A browser runs the module on the page's engine, at 1.1× Rust compiled to WebAssembly.
[export] native_scriptsputs the library beside the executable, in an iOS app's frameworks, or in an APK.- A saved file reloads in 2.3 ms on wasmtime, keeping each field by name.
The scripting manual has the whole language.