cli  
 
Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

Preface

Goal: Create a native NodeJS addon using Rust.

There are so many rust potential of rust, varying degree from concurrency, to WASM. One that ctach my attenti0on is the ability, to create a native NodeJS addon easily.

This is a bonus article. This will be short.

The Alternative

So far there are two library to do this, The first is neon binding, and the second is the napi.rs as a new contender:

  1. https://neon-bindings.com/
  2. https://napi.rs/

08: Neon Serde

Passing JSON

Consider go straight with working example. This is actually just neon binding with macros.

Rust Native

From the json example, in neon bindingofficial documentation. and unique code from previous example, we can easily develop this:

use neon::register_module;
use neon_serde::export;
use serde_derive::{Deserialize, Serialize};

mod my_utils;

#[derive(Serialize, Deserialize, Debug)]
struct Song {
    title: String,
    tags:  Option<Vec<String>>
}

export! {
    fn passRecords(songs: Vec<Song>) -> String {
        let tags: Vec<String> = songs
            .into_iter()
            .filter_map(|song| song.tags)
            .flat_map(|tag_s| tag_s)
            .collect();

        format!("{:?}", my_utils::unique(tags))
    }
}

We need to compile first.

$ neon build --release
neon info running cargo
   Compiling cc v1.0.41
   Compiling memchr v2.2.1
   …
   Compiling neon-serde v0.2.0
    Finished release [optimized] target(s) in 2m 49s
neon info generating native/index.node

A new file native/index.node should be created by now.

Custom Utility Module

It is using the code from previous article.

NodeJS Script

Maybe Null 🤔?

I would like to point how Option handled nicely. We need an example data in javascript file, with a record containing null value.

const {
  passRecords
} = require('../native/index.node');

const songs  = [
  { title: "Cantaloupe Island",
    tags: ["60s", "jazz"] },
  { title: "Let It Be",
    tags: ["60s", "rock"] },
  { title: "Knockin' on Heaven's Door",
    tags: ["70s", "rock"] },
  { title: "Emotion",
    tags: ["70s", "pop"] },
  { title: "The River" }
];

console.log(
  "Unique Tags: ",
  passRecords(songs)
);

Consider test the native script:

$ node lib/index.js
Unique Tags:  ["60s", "jazz", "rock", "70s", "pop"]

Rust Native NodeJS: Passing JSON

This wouldn’t be so hard.


09: Neon Binding

We can go low level using neon binding without macro.

Rust Native Skeleton

There are four examples here.

  1. Hello World (only string result)
  2. Passing String (with string result)
  3. Passing Array (with array result)
  4. Passing Object (with array result)

The skeleton of the code is shown below:

use neon::prelude::*;

fn hello(mut cx: FunctionContext) -> JsResult<JsString> {…}

fn pass_str(mut cx: FunctionContext) -> JsResult<JsString> {…}

fn pass_array(mut cx: FunctionContext) -> JsResult<JsArray> {…}

fn pass_object(mut cx: FunctionContext) -> JsResult<JsArray> {…}

register_module!(mut cx, {
    cx.export_function("hello", hello)?;
    cx.export_function("passStr", pass_str)?;
    cx.export_function("passArray", pass_array)?;
    cx.export_function("passObject", pass_object)?;
    Ok(())
});

Rust Native NodeJS: Passing and Casting: Rust Code

NodeJS: Complete

This is the complete javascript file.

const {
  hello,
  passStr,
  passArray,
  passObject
} = require('../native/index.node');

console.log(hello(), "\n");

console.log(passStr("Cantaloupe Island"), "\n");

console.log(passArray(["60s", "jazz"]), "\n");

console.log(passObject({
  title: "Cantaloupe Island",
  tags:  ["60s", "jazz"]
}));

With the result similar as below:

$ node lib/index.js
Aloha Done 

| In Rust: Cantaloupe Island
Cantaloupe Island 

| In Rust: ["60s", "jazz"]
[ '60s', 'jazz' ] 

| Tags in Rust: ["60s", "jazz"]
[ '60s', 'jazz' ]

Rust Native NodeJS: Passing and Casting: Javascript Output

Rust Native: Hello

We can start from simple, from the official example.

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

And if we strip down the javascript to this:

const { hello } = require('../native/index.node');

console.log(hello());

We can have the result similar as below:

$ node lib/index.js
Aloha Done

We haven’t pass anything yet to rust.

Rust Native: Passing String

Still from the official documentation example. We can pass astring as parameter argument.

fn pass_str(mut cx: FunctionContext) -> JsResult<JsString> {
    let string = cx.argument::<JsString>(0)?.value();
    println!("| In Rust: {}", string);
    Ok(cx.string(string))
}

And if we strip down the javascript to this:

const { passStr } = require('../native/index.node');

console.log(passStr("Cantaloupe Island"));

We can have the result similar as below:

$ node lib/index.js
| In Rust: Cantaloupe Island
Cantaloupe Island

We have pass JsString type to rust, and cast to rust’s native String type.

Rust Native: Cast Helper

Before we continue, we need two helper functions to cast from javascript type, into rust’s native String type.

First the string element.

fn jsvalue_to_string(
    js_value: Handle<JsValue>, default: Handle<JsString>
) -> String {
    js_value
        .downcast::<JsString>()
        .unwrap_or(default)
        .value()
}

And then the vector, containing those string elements.

fn vec_jsvalue_to_string(
    vec_value: Vec<Handle<JsValue>>, default: Handle<JsString>
) -> Vec<String> {
    let vec_string: Vec<String> = vec_value
        .iter()
        .map(|js_value| { jsvalue_to_string(*js_value, default) })
        .collect();

    vec_string
}

Rust Native: Passing Array

The casting process is getting more complex now. The thing is we have to deal with, the difference between rust vector and JsArray.

fn pass_array(mut cx: FunctionContext) -> JsResult<JsArray> {
    // JsArray to Vec
    let js_array_ori: Handle<JsArray> = cx.argument::<JsArray>(0)?;

    let default: Handle<JsString> = cx.string("");
    let vec_value: Vec<Handle<JsValue>> =
        js_array_ori.to_vec(&mut cx)?;
    let vec_string: Vec<String> =
        vec_jsvalue_to_string(vec_value, default);
    
    println!("| In Rust: {:?}", vec_string);

    // Vec to JsArray
    let js_array_new = JsArray::new(&mut cx, vec_string.len() as u32);
    for (i, obj) in vec_string.iter().enumerate() {
        let js_string = cx.string(obj);
        js_array_new.set(&mut cx, i as u32, js_string).unwrap();
    }
    Ok(js_array_new)
}

With the javascript as below:

const { passArray } = require('../native/index.node');

console.log(passArray(["60s", "jazz"]));

We can have the result similar as below:

$ node lib/index.js
| In Rust: ["60s", "jazz"]
[ '60s', 'jazz' ]

Rust Native: Passing Object

If the above haven’t got you so much challenge, we can contiune with passing our previous song record as objects.

fn pass_object(mut cx: FunctionContext) -> JsResult<JsArray> {
    let js_object_handle: Handle<JsObject> = cx.argument(0)?;

    let js_object = js_object_handle
        .downcast::<JsObject>()
        .unwrap_or(JsObject::new(&mut cx));

    let js_array_default: Handle<JsArray> =
        JsArray::new(&mut cx, 0 as u32);

    let js_array_new: Handle<JsArray> =
        js_object
            .get(&mut cx, "tags")?
            .downcast::<JsArray>()
            .unwrap_or(js_array_default);

    let default: Handle<JsString> = cx.string("");
    let vec_value: Vec<Handle<JsValue>> =
        js_array_new.to_vec(&mut cx)?;
    let vec_string: Vec<String> =
        vec_jsvalue_to_string(vec_value, default);
    
    println!("| Tags in Rust: {:?}", vec_string);

    Ok(js_array_new)
}

Then consider give a paramater argument, as input in the javascript as below:

console.log(passObject({
  title: "Cantaloupe Island",
  tags:  ["60s", "jazz"]
}));

We can have the result similar as below:

$ node lib/index.js
| Tags in Rust: ["60s", "jazz"]
[ '60s', 'jazz' ]' ]

Rust Native NodeJS: Passing and Casting: Object

We are done.


What is Next 🤔?

I thinks that’s all.

Consider continue reading [ C# - Playing with Records - Part One ].