README
On this page
Native Fusion Example
Native Fusion lets you write performance-critical functions and stateful objects in Rust and use them directly from BoxLang. Everything is statically linked into a single native binary — no external libraries, no FFI boilerplate at call sites.
This example demonstrates both interop styles:
| Style | Rust side | BoxLang side |
|---|---|---|
| Standalone BIF | #[matchbox_fn] on a plain function | result = fast_fib(10) |
| Native object | #[matchbox_class] + #[matchbox_methods] on a struct | s = new rust:stats.RunningStats() |
Project Layout
native_fusion/
├── app.bxs ← BoxLang entry point
└── native/
└── stats.rs ← Rust source: BIFs + RunningStats native object
MatchBox automatically detects any .rs files inside a native/ directory
that lives alongside your entry script and compiles them together with the VM.
Building
You need a Rust toolchain (cargo, rustc) in addition to the matchbox binary.
cd docs/examples/native_fusion
matchbox --target native app.bxs
MatchBox will:
- Compile
native/stats.rstogether with the MatchBox VM. - Register the exported BIFs (
fast_fib,fast_factorial) and theRunningStatsclass. - Compile
app.bxsto bytecode and embed it. - Produce a single
appbinary.
Running
./app
Expected output:
=== Native Fusion Demo ===
--- Fibonacci (BIF) ---
fib(0) = 0
fib(1) = 1
fib(2) = 1
...
fib(10) = 55
--- Factorials (BIF) ---
1! = 1
2! = 2
...
10! = 3628800
--- RunningStats (native object) ---
count : 6
sum : 108
mean : 18
min : 4
max : 42
All results computed in Rust — zero JVM, zero overhead.
Writing a Native BIF with #[matchbox_fn]
Using the #[matchbox_fn] macro is the recommended way to write BIFs. You write
a plain typed Rust function — the macro generates the {name}_wrapper with the
correct BIF signature, argument-count validation, and type coercions for you.
use matchbox_vm::{matchbox_fn};
// Write a normal Rust function with typed parameters.
// Supported types: f64, i32, bool, String, BxValue (pass-through)
#[matchbox_fn]
pub fn my_bif(n: f64) -> f64 {
n * 2.0
}
The macro expands this into two things:
- Your original
my_bif(n: f64) -> f64function, unchanged. - A generated
my_bif_wrapper(vm, args) -> Result<BxValue, String>that the VM actually calls.
Each .rs file inside native/ must export a register_bifs() function.
Register the _wrapper variant (not the original function):
use matchbox_vm::{matchbox_fn, types::{BxNativeFunction, BxValue}};
use std::collections::HashMap;
#[matchbox_fn]
pub fn my_bif(n: f64) -> f64 {
n * 2.0
}
pub fn register_bifs() -> HashMap<String, BxNativeFunction> {
let mut map = HashMap::new();
// Register the macro-generated wrapper, not the plain function.
map.insert("my_bif".to_string(), my_bif_wrapper as BxNativeFunction);
map
}
The string key ("my_bif") is the name BoxLang uses to call the function:
result = my_bif(21)
println(result) // 42
Supported Parameter Types
| Rust type | Macro conversion |
|---|---|
f64 | args[i].as_number() |
i32 | args[i].as_int() |
bool | args[i].as_bool() |
String | vm.to_string(args[i]) |
BxValue | passed through as-is |
Native Objects with #[matchbox_class] and #[matchbox_methods]
For stateful interop, expose a Rust struct as a BoxLang native object.
use matchbox_vm::{
matchbox_class, matchbox_methods,
types::{BxNativeFunction, BxNativeObject, BxValue, BxVM},
};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
// #[matchbox_class] auto-implements the BxNativeObject trait,
// wiring get_property / set_property / call_method to the
// dispatcher generated by #[matchbox_methods].
#[matchbox_class]
#[derive(Debug)]
pub struct Counter {
pub value: f64,
}
// #[matchbox_methods] generates a dispatch_method() router that maps
// BoxLang method names (case-insensitive) to the Rust methods below.
#[matchbox_methods]
impl Counter {
pub fn increment(&mut self) -> f64 {
self.value += 1.0;
self.value
}
pub fn add(&mut self, n: f64) -> f64 {
self.value += n;
self.value
}
pub fn get(&self) -> f64 {
self.value
}
}
// Constructor — called when BoxLang evaluates `new rust:mymodule.Counter(0)`.
// vm.native_object_new() stores the object on the VM heap and returns an id
// wrapped in a BxValue pointer.
pub fn create_counter(vm: &mut dyn BxVM, args: &[BxValue]) -> Result<BxValue, String> {
let initial = args.first().map(|v| v.as_number()).unwrap_or(0.0);
let obj = Counter { value: initial };
let id = vm.native_object_new(Rc::new(RefCell::new(obj)));
Ok(BxValue::new_ptr(id))
}
// register_classes() maps "<module>.<ClassName>" to the constructor.
// The module name is the filename of this .rs file without the extension.
// BoxLang resolves `new rust:mymodule.Counter()` using this key.
pub fn register_classes() -> HashMap<String, BxNativeFunction> {
let mut map = HashMap::new();
map.insert("mymodule.Counter".to_string(), create_counter as BxNativeFunction);
map
}
BoxLang:
c = new rust:mymodule.Counter(0)
c.increment()
c.add(9)
println(c.get()) // 10
When to Use Native Fusion
- You need maximum throughput on a hot path (parsing, compression, crypto, ML).
- You want to use a Rust crate (
serde,image,reqwest, etc.) from BoxLang. - You need direct OS or hardware access.
For most application logic, plain BoxLang is sufficient. Reserve Native Fusion for the innermost loops where performance is measured and critical.
Limitations
| Feature | Status |
|---|---|
| Available in native builds | ✅ |
| Available in WASM builds | ❌ Not supported |
Multiple .rs files in native/ | ✅ Each may export register_bifs() and/or register_classes() |
| Using external Rust crates | ✅ Add a Cargo.toml to native/ |
Mutable self in #[matchbox_methods] | ✅ Dispatcher takes &mut self |
| Property access on native objects | ⚠️ Override get_property in BxNativeObject manually |
For the full macro API reference see native-fusion.md.