Appearance
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]callsexport_functionand/orneon::registered().export() - [ ] Arguments are read with a specific
Js*type (validates) orcx.args()(destructures) - [ ] Optional arguments use
argument_optorOption<T>in the args tuple - [ ]
npm run buildsucceeds and the names appear onrequire('.')
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.