Skip to content

Calling JavaScript from Rust

Fresh 🌱

Beyond returning values, Rust often needs to call back into JavaScript: invoke a callback, call a method on an object, construct a class, or read a global. The modern way to do this is the bind() builder, which converts Rust arguments to JS for you.

The bind() builder

bind() produces a builder where each .arg(...) adds a typed argument and a terminal method runs the call:

rust
use neon::prelude::*;

fn call_js_functions(mut cx: FunctionContext) -> JsResult<JsValue> {
    // A function passed in as the first argument
    let callback: Handle<JsFunction> = cx.argument(0)?;

    // Call with arguments using bind()
    let result: Handle<JsValue> = callback
        .bind(&mut cx)
        .arg("hello")?
        .arg(42)?
        .call()?;

    // Call with an explicit `this`
    let obj = cx.empty_object();
    let method: Handle<JsFunction> = obj.prop(&mut cx, "someMethod").get()?;
    method
        .bind(&mut cx)
        .this(obj)?
        .arg("data")?
        .call()?;

    // Call as a constructor, like `new Array(10)`
    let array_ctor: Handle<JsFunction> = cx.global("Array")?;
    let new_array: Handle<JsObject> = array_ctor
        .bind(&mut cx)
        .arg(10)?
        .construct()?;

    // Call a global function
    let parse_int: Handle<JsFunction> = cx.global("parseInt")?;
    let num: f64 = parse_int
        .bind(&mut cx)
        .arg("42")?
        .arg(10)? // radix
        .call()?;

    Ok(result)
}

The terminal methods:

MethodEquivalent JavaScript
.call()fn(args...)
.this(x)?.call()fn.call(x, args...)
.construct()new fn(args...)
.exec()call for side effects, ignore the return value

Two builder styles

You may also see the older .call_with(&mut cx).arg(handle).apply(&mut cx)? and .construct_with(...) builders (covered in Functions). bind() is the newer, more ergonomic form because it accepts plain Rust values and converts them.

Holding JS values across threads: Root

A Handle is only valid within the current synchronous context. The moment you cross into another thread or an async callback, you need a Root - a persistent reference that keeps a JavaScript value alive and can be turned back into a handle later.

rust
use neon::prelude::*;

struct AsyncState {
    callback: Root<JsFunction>,
    data: Root<JsObject>,
}

fn setup_async(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let callback = cx.argument::<JsFunction>(0)?;
    let data = cx.argument::<JsObject>(1)?;

    // Root the values so they survive past this function
    let state = AsyncState {
        callback: callback.root(&mut cx),
        data: data.root(&mut cx),
    };

    let channel = cx.channel();

    std::thread::spawn(move || {
        // Back on the JS main thread, turn Roots into live handles
        channel.send(move |mut cx| {
            let callback = state.callback.into_inner(&mut cx);
            let data = state.data.into_inner(&mut cx);
            callback.bind(&mut cx).arg(data)?.exec()?;
            Ok(())
        });
    });

    Ok(cx.undefined())
}

The pattern is always the same:

  1. value.root(&mut cx) to persist it,
  2. move the Root into the thread/closure,
  3. root.into_inner(&mut cx) to get a handle back on the main thread (via a Channel),
  4. then call or use it.

You cannot touch JS off the main thread

JavaScript values may only be created or called on the main thread. A background thread holds a Root and uses a Channel to schedule the actual JS work back on the main thread. See Async Tasks, Promises & Channels.