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:

StyleRust sideBoxLang side
Standalone BIF#[matchbox_fn] on a plain functionresult = fast_fib(10)
Native object#[matchbox_class] + #[matchbox_methods] on a structs = 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:

  1. Compile native/stats.rs together with the MatchBox VM.
  2. Register the exported BIFs (fast_fib, fast_factorial) and the RunningStats class.
  3. Compile app.bxs to bytecode and embed it.
  4. Produce a single app binary.

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:

  1. Your original my_bif(n: f64) -> f64 function, unchanged.
  2. 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 typeMacro conversion
f64args[i].as_number()
i32args[i].as_int()
boolargs[i].as_bool()
Stringvm.to_string(args[i])
BxValuepassed 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

FeatureStatus
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.

Edit this page Download Markdown Last updated Sep 30, 2026, 8:17:14 PM