# Native Fusion — Rust Interop Reference

**Native Fusion** is the mechanism that lets you write Rust functions and classes
and expose them as first-class BoxLang Built-In Functions (BIFs) and native
objects. Everything is statically linked into your final binary — no shared
libraries, no FFI boilerplate at the BoxLang call site.

> Native Fusion is available in **native builds only** (`--target native`).  
> It is not available in WASM targets.

---

## How It Works

1. Create a `native/` directory alongside your `.bxs` entry script.
2. Write one or more `.rs` files inside `native/`.
3. Each `.rs` file exports a `register_bifs()` and/or `register_classes()` function.
4. Run `matchbox --target native app.bxs`.

MatchBox detects `native/`, compiles the Rust files together with the VM, merges
all BIF and class registrations, and produces a single binary.

---

## Macros

All interop macros are re-exported from the `matchbox_vm` crate — no separate
`matchbox_macros` import is needed:

```rust
use matchbox_vm::{matchbox_fn, matchbox_class, matchbox_methods};
```

---

### `#[matchbox_fn]` — Expose a Rust function as a BoxLang BIF

Annotate a plain Rust function with typed parameters. The macro generates a
corresponding `{name}_wrapper` function that carries the low-level BIF signature
expected by the VM (`fn(&mut dyn BxVM, &[BxValue]) -> Result<BxValue, String>`).

The wrapper handles argument-count validation and type coercion automatically.

#### Usage

```rust
use matchbox_vm::{matchbox_fn, types::{BxNativeFunction}};
use std::collections::HashMap;

#[matchbox_fn]
pub fn add(a: f64, b: f64) -> f64 {
    a + b
}

pub fn register_bifs() -> HashMap<String, BxNativeFunction> {
    let mut map = HashMap::new();
    // Register the generated wrapper, not the original function.
    map.insert("add".to_string(), add_wrapper as BxNativeFunction);
    map
}
```

BoxLang:

```boxlang
result = add(10, 20)
println(result)   // 30
```

#### Supported Parameter Types

| Rust parameter type | VM conversion applied by the macro |
|---|---|
| `f64` | `args[i].as_number()` |
| `i32` | `args[i].as_int()` |
| `bool` | `args[i].as_bool()` |
| `String` | `vm.to_string(args[i])` |
| `BxValue` | passed through unchanged |

The return value is always wrapped as `BxValue::new_number(result as f64)`.
For BIFs that need to return other types (strings, null, objects), write a manual
BIF instead (see [Manual BIF Signature](#manual-bif-signature)).

#### What the Macro Generates

```rust
// Your function — unchanged:
pub fn add(a: f64, b: f64) -> f64 { a + b }

// Generated wrapper:
pub fn add_wrapper(
    vm: &mut dyn matchbox_vm::types::BxVM,
    args: &[matchbox_vm::types::BxValue],
) -> Result<matchbox_vm::types::BxValue, String> {
    if args.len() != 2 {
        return Err(format!("add requires 2 arguments, got {}", args.len()));
    }
    let a = args[0].as_number();
    let b = args[1].as_number();
    let result = add(a, b);
    Ok(matchbox_vm::types::BxValue::new_number(result as f64))
}
```

---

### `#[matchbox_class]` — Expose a Rust struct as a BoxLang native object

Annotate a struct to auto-implement the `BxNativeObject` trait. The generated
implementation delegates `get_property`, `set_property`, and `call_method` to a
`dispatch_method` function that is generated by `#[matchbox_methods]`.

Always pair `#[matchbox_class]` with a `#[matchbox_methods]` impl block.

#### Usage

```rust
use matchbox_vm::{matchbox_class, matchbox_methods, types::{BxValue, BxVM, BxNativeFunction, BxNativeObject}};
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;

#[matchbox_class]
#[derive(Debug)]
pub struct Counter {
    pub value: f64,
}

#[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 — registered separately, not via #[matchbox_fn].
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))
}

pub fn register_classes() -> HashMap<String, BxNativeFunction> {
    let mut map = HashMap::new();
    // Key format: "<module_filename>.<ClassName>"
    // This file is native/counter.rs → module name is "counter"
    map.insert("counter.Counter".to_string(), create_counter as BxNativeFunction);
    map
}
```

BoxLang (instantiate with `new rust:<module>.<Class>(...)`):

```boxlang
c = new rust:counter.Counter(0)
c.increment()
c.add(9)
println(c.get())   // 10
```

---

### `#[matchbox_methods]` — Generate a method dispatcher for a native class

Annotate an `impl` block to generate a `dispatch_method` function that routes
BoxLang method calls to the correct Rust method by name (case-insensitive).

`#[matchbox_fn]` type-coercion rules apply to each method's parameters.

#### Usage

```rust
#[matchbox_methods]
impl MyStruct {
    pub fn process(&self, input: f64) -> f64 {
        input * self.factor
    }
}
```

BoxLang calls `obj.process(42)` and the dispatcher resolves it to
`MyStruct::process(&self, 42.0)`.

#### What the Macro Generates

```rust
impl MyStruct {
    fn dispatch_method(
        &mut self,
        vm: &mut dyn BxVM,
        name: &str,
        args: &[BxValue],
    ) -> Result<BxValue, String> {
        match name.to_lowercase().as_str() {
            "process" => {
                if args.len() != 1 { return Err(...); }
                let input = args[0].as_number();
                let result = self.process(input);
                Ok(BxValue::new_number(result as f64))
            }
            _ => Err(format!("Method {} not found", name)),
        }
    }
}
```

---

### `#[matchbox_module]` — Module marker (no-op)

A no-op attribute that serves as documentation: apply it to the top-level `mod`
or `impl` to indicate the file is a Native Fusion module. Currently passes
through unchanged.

```rust
#[matchbox_module]
mod my_native_module { ... }
```

---

## Registration Entry Points

Every `.rs` file in `native/` may export two entry-point functions. MatchBox
calls them both at startup.

### `register_bifs()` — standalone functions

```rust
pub fn register_bifs() -> HashMap<String, BxNativeFunction> {
    let mut map = HashMap::new();
    map.insert("my_bif".to_string(), my_bif_wrapper as BxNativeFunction);
    map
}
```

BIF names must be unique across all files in `native/`.

### `register_classes()` — native object constructors

```rust
pub fn register_classes() -> HashMap<String, BxNativeFunction> {
    let mut map = HashMap::new();
    // Key: "<module>.<ClassName>" where <module> is the filename without .rs
    map.insert("mymodule.MyClass".to_string(), create_my_class as BxNativeFunction);
    map
}
```

BoxLang uses the key to resolve `new rust:mymodule.MyClass(...)`.

---

## Manual BIF Signature

When you need full control (e.g. returning a string, null, or an object pointer),
skip `#[matchbox_fn]` and write the low-level signature directly:

```rust
use matchbox_vm::types::{BxNativeFunction, BxValue, BxVM};
use std::collections::HashMap;

pub fn greet(vm: &mut dyn BxVM, args: &[BxValue]) -> Result<BxValue, String> {
    if args.is_empty() {
        return Err("greet: expected one argument".to_string());
    }
    let name = vm.to_string(args[0]);
    Ok(BxValue::new_string(format!("Hello, {}!", name)))
}

pub fn register_bifs() -> HashMap<String, BxNativeFunction> {
    let mut map = HashMap::new();
    map.insert("greet".to_string(), greet as BxNativeFunction);
    map
}
```

---

## Project Layout

```
my_app/
├── app.bxs         ← BoxLang entry point
└── native/
    ├── math.rs     ← exports register_bifs()
    ├── counter.rs  ← exports register_bifs() + register_classes()
    └── crypto.rs   ← exports register_classes()
```

Multiple files are supported as long as BIF and class names are unique across
all files.

---

## Using External Rust Crates

Add a `Cargo.toml` to the `native/` directory to pull in dependencies:

```toml
[package]
name    = "native"
version = "0.1.0"
edition = "2021"

[dependencies]
serde       = { version = "1", features = ["derive"] }
serde_json  = "1"
base64      = "0.22"
```

MatchBox compiles this as a standard Cargo project and links the output into the
final binary.

---

## Limitations

| Feature | Status |
|---|---|
| Native builds (`--target native`) | ✅ |
| WASM builds (`--target js` / `--target wasm`) | ❌ Not supported |
| Multiple `.rs` files in `native/` | ✅ |
| External Rust crates via `native/Cargo.toml` | ✅ |
| Return types other than `f64` from `#[matchbox_fn]` | ⚠️ Use manual BIF signature |
| Mutable `self` in `#[matchbox_methods]` | ✅ Works — dispatcher takes `&mut self` |
| Property access on native objects (`obj.field`) | ⚠️ Override `get_property` in `BxNativeObject` |

See [native-builds.md](native-builds.md) for the complete native build reference
and [differences-from-boxlang.md](../differences-from-boxlang.md) for the full
list of features not available in MatchBox.
