Skip to content

Boxed Native Data (JsBox)

Fresh 🌱

Sometimes you do not want to copy Rust data into a JavaScript object - you want JavaScript to hold an opaque handle to a live Rust value and call back into it. That is what JsBox is for: it embeds a Rust value inside a JavaScript object, with the garbage collector managing its lifetime.

The Finalize trait

Any type you box must implement Finalize. For many types the empty default implementation is enough; Finalize is the hook Neon calls when the JavaScript garbage collector reclaims the box, letting you run cleanup if needed.

rust
struct User {
    first_name: String,
    last_name: String,
}

impl Finalize for User {}

Boxing a value

cx.boxed(value) wraps a Rust value as a JsBox<T>, which is a JavaScript value you can return or store:

rust
fn create_user(mut cx: FunctionContext) -> JsResult<JsBox<User>> {
    let first_name = cx.argument::<JsString>(0)?.value(&mut cx);
    let last_name = cx.argument::<JsString>(1)?.value(&mut cx);
    Ok(cx.boxed(User { first_name, last_name }))
}

Reading a boxed value back

Accept the box as an argument and read through it. A JsBox<T> derefs to &T, so you call methods on the inner value directly:

rust
impl User {
    fn full_name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}

fn user_full_name(mut cx: FunctionContext) -> JsResult<JsString> {
    let user = cx.argument::<JsBox<User>>(0)?;
    let full_name = user.full_name();
    Ok(cx.string(full_name))
}

Wrapping it idiomatically in JavaScript

A box is opaque on the JS side - it is just a handle. The common pattern is a small JavaScript class that hides the box and exposes clean methods:

javascript
const addon = require('.');

class User {
    constructor(firstName, lastName) {
        this.boxed = addon.createUser(firstName, lastName);
    }

    fullName() {
        return addon.userFullName(this.boxed);
    }
}

const u = new User('Ada', 'Lovelace');
console.log(u.fullName()); // "Ada Lovelace"

Copy vs. box - which do you want?

flowchart TD
    Q{Does JS need to keep a live
handle to a Rust value?} -->|No, just read fields once| COPY["Convert to a plain JS object
(see Objects guide)"] Q -->|Yes, call back into it later| BOX["Box it with cx.boxed + Finalize"] style COPY fill:#6366f1,color:#fff style BOX fill:#f59e0b,color:#fff
NeedApproach
One-time snapshot of fields into JSConvert the struct to a JsObject
JavaScript holds and reuses a Rust valueJsBox<T> with Finalize

Interior mutability

A JsBox<T> gives shared (&T) access. To mutate boxed state across calls, store interior-mutable types inside (for example a RefCell or a thread-safe cell) so you can borrow mutably at call time.

Shared ownership, GC-timed cleanup

The boxed value lives until the JavaScript garbage collector reclaims the wrapper, at which point Finalize runs. Do not assume deterministic, immediate cleanup - it happens on the GC's schedule.