Appearance
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| Need | Approach |
|---|---|
| One-time snapshot of fields into JS | Convert the struct to a JsObject |
| JavaScript holds and reuses a Rust value | JsBox<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.