Skip to content

SOP: Export Functions to JavaScript

Fresh 🌱

Goal: Make Rust functions callable from JavaScript. Neon offers two styles: the explicit export_function call inside #[neon::main], and the ergonomic #[neon::export] attribute that wires things up for you.

Done when: Your exported Rust functions are reachable from require('.') in Node.

Approach A - Explicit export in #[neon::main]

This is the foundational pattern. You define a function of type fn(FunctionContext) -> JsResult<T> and export it by name.

rust
use neon::prelude::*;

fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
    Ok(cx.string("hello node"))
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    // Export a function
    cx.export_function("hello", hello)?;

    // You can also export a plain value
    let version = cx.string("1.0.0");
    cx.export_value("version", version)?;

    Ok(())
}

The main function itself returns a NeonResult, because exporting interacts with the module object and could throw. The ? operator propagates any error.

From JavaScript:

javascript
const addon = require('.');
console.log(addon.hello());   // "hello node"
console.log(addon.version);   // "1.0.0"

Approach B - The #[neon::export] attribute

The #[neon::export] macro removes the boilerplate. It registers the function automatically and converts plain Rust argument and return types to and from JavaScript. It also converts snake_case Rust names to camelCase JS names.

rust
use neon::prelude::*;
use neon::types::extract::Error;

// Exported to JavaScript as "addNumbers"
#[neon::export]
fn add_numbers(a: f64, b: f64) -> f64 {
    a + b
}

// Override the exported name
#[neon::export(name = "multiply")]
fn mul(a: f64, b: f64) -> f64 {
    a * b
}

// Need the context? Take `&mut Cx` as the first parameter
#[neon::export]
fn create_object<'cx>(cx: &mut Cx<'cx>, name: String) -> JsResult<'cx, JsObject> {
    let obj = cx.empty_object();
    let name_val = cx.string(&name);
    obj.set(cx, "name", name_val)?;
    Ok(obj)
}

// Return a Result to throw on the JS side automatically
#[neon::export]
fn read_file(path: String) -> Result<String, Error> {
    let contents = std::fs::read_to_string(path)?;
    Ok(contents)
}

Collecting #[neon::export] functions in main

When you use #[neon::export], register all of them at once inside your module entry point:

rust
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    // Export everything tagged with #[neon::export]
    neon::registered().export(&mut cx)?;
    Ok(())
}

You can mix both styles - call export_function for some and neon::registered().export() for the rest.

Reading and validating arguments

Inside a FunctionContext function, read positional arguments with cx.argument(). Choosing a specific JS type validates and casts it; a mismatch throws a TypeError:

rust
fn create_book(mut cx: FunctionContext) -> JsResult<JsObject> {
    let title = cx.argument::<JsString>(0)?;
    let author = cx.argument::<JsString>(1)?;
    let year = cx.argument::<JsNumber>(2)?;

    let obj = cx.empty_object();
    obj.set(&mut cx, "title", title)?;
    obj.set(&mut cx, "author", author)?;
    obj.set(&mut cx, "year", year)?;
    Ok(obj)
}

With the #[neon::export] macro you can instead destructure all arguments at once via cx.args():

rust
#[neon::export]
fn add(mut cx: FunctionContext) -> JsResult<JsNumber> {
    let (a, b): (f64, f64) = cx.args()?;
    Ok(cx.number(a + b))
}

Option<T> in the tuple marks a trailing argument as optional:

rust
#[neon::export]
fn greet(mut cx: FunctionContext) -> JsResult<JsString> {
    let (greeting, name, suffix): (String, String, Option<String>) = cx.args()?;
    let msg = format!("{}, {}{}!", greeting, name, suffix.unwrap_or_default());
    Ok(cx.string(msg))
}

Checklist

  • [ ] Each function has type fn(FunctionContext) -> JsResult<T>, or uses #[neon::export] with plain types
  • [ ] #[neon::main] calls export_function and/or neon::registered().export()
  • [ ] Arguments are read with a specific Js* type (validates) or cx.args() (destructures)
  • [ ] Optional arguments use argument_opt or Option<T> in the args tuple
  • [ ] npm run build succeeds and the names appear on require('.')

snake_case → camelCase

#[neon::export] fn add_numbers is exported as addNumbers. Use #[neon::export(name = "...")] when you need an exact JS name.

See Functions for calling and constructing JS functions, and Error Handling for throwing exceptions cleanly.