Rust-Node bindgen: node-bindgen - Easy way to generate nodejs module using Rust

node-bindgen

Easy way to write native Node.js module using idiomatic Rust

Features

  • Easy: Just write idiomatic Rust code, node-bindgen take care of generating Node.js FFI wrapper codes.
  • Safe: Node.js arguments are checked automatically based on Rust types.
  • Async: Support Async Rust. Async codes are translated into Node.js promises.
  • Class: Rust struct can be access using Node.js classes.
  • Stream: Implement Node.js stream using Rust
  • N-API: Use Node.js N-API which means you don't have to recompile your module.

Compatibility with Node.js version

This project uses v5 of Node N-API. Please see following compatibility matrix.

Following OS are supported:

  • Linux
  • MacOs
  • Windows

Why node-bindgen?

Writing native node-js requires lots of boiler plate code. Node-bindgen generates external "C" glue code from rust code including native module registration. This make it writing node-js module easy and fun.

Node-bi

Getting started

CLI Installation

Install nj-cli command line which will be used to generate native library.

cargo install nj-cli

This is one time step.

Configuring Cargo.toml

Add two dependencies to your projects' Cargo.toml.

Add node-bindgen as a regular dependency (as below):

[dependencies]
node-bindgen = { version = "2.0.0-beta.1" }

Then add node-bindgen's procedure macro to your build-dependencies as below:

[build-dependencies]
node-bindgen = { path = "../../", features = ["build"] }

Then update crate type to cdylib to generate node.js compatible native module:

[lib]
crate-type = ["cdylib"]

Example

A simple functional example which add two integers. Noticed that you don't need to worry about JS conversion.

use node_bindgen::derive::node_bindgen;

/// add two integer
#[node_bindgen]
fn sum(first: i32, second: i32) -> i32 {        
    first + second
}

Building native library

To build node.js library, using nj-cli to build:

nj-cli build

This will generate Node.js module in "./dist" folder.

Using in Node.js

Then in the Node.js, rust function can invoked as normal node.js function:

$ node
Welcome to Node.js v14.0.0.
Type ".help" for more information.
> let addon = require('./dist');
undefined
> addon.sum(2,3)
5
> 

Features

Function name or method can be renamed instead of default mapping

#[node_bindgen(name="multiply")]
fn mul(first: i32,second: i32) -> i32 {        
    first * second 
}

Rust function mul is re-mapped as multiply

Optional argument

Argument can be skipped if it is marked as optional

#[node_bindgen]
fn sum(first: i32, second: Option<i32>) -> i32 {        
    first + second.unwrap_or(0)
}

Then sum can be invoked as sum(10) or sum(10,20)

Callback

JS callback are mapped as Rust closure

#[node_bindgen]
fn hello<F: Fn(String)>(first: f64, second: F) {

    let msg = format!("argument is: {}", first);

    second(msg);
}

from node:

let addon = require('./dist');

addon.hello(2,function(msg){
  assert.equal(msg,"argument is: 2");
  console.log(msg);  // print out argument is 2
});

Callback are supported in Async rust as well.

Support for Async Rust

Async rust function is mapped to Node.js promise.

use std::time::Duration;
use flv_future_aio::time::sleep;
use node_bindgen::derive::node_bindgen;


#[node_bindgen]
async fn hello(arg: f64) -> f64 {
    println!("sleeping");
    sleep(Duration::from_secs(1)).await;
    println!("woke and adding 10.0");
    arg + 10.0
}
let addon = require('./dist');

addon.hello(5).then((val) => {
  console.log("future value is %s",val);
});

JavaScript class

JavaScript class is supported.

struct MyClass {
    val: f64,
}


#[node_bindgen]
impl MyClass {

    #[node_bindgen(constructor)]
    fn new(val: f64) -> Self {
        Self { val }
    }

    #[node_bindgen]
    fn plus_one(&self) -> f64 {
        self.val + 1.0
    }

    #[node_bindgen(getter)]
    fn value(&self) -> f64 {
        self.val
    }
}
let addon = require('./dist');
const assert = require('assert');

let obj = new addon.MyObject(10);
assert.equal(obj.value,10,"verify value works");
assert.equal(obj.plusOne(),11);

There are more features in the examples folder

Contributing

If you'd like to contribute to the project, please read our Contributing guide.

License

This project is licensed under the Apache license.

Comments

  • Threads
    Threads

    May 26, 2020

    New to rust, I am trying to have my callback function be passed into my thread, this call back function will be called multiple times. However, it seems I can't have my cb shared between threads. ``Fcannot be shared between threads safely

    Reply
  • Return functions
    Return functions

    May 27, 2020

    Any way to do this

    addon.foo((string)=>{
      string = "slkdfjsf";
      return string;
    })
    

    or this

    addon.foo((func)=>{
      func("slkdfjsf")
    })
    
    Reply
  • Can I await javascript promise from rust?
    Can I await javascript promise from rust?

    Jun 8, 2020

    Hi. I'm the author of swc and I'm considering switching from neon to node-bindgen.

    Can I store js function on struct and call it from other threads? Neon supports it via EventHandler.

    Also, I need to wait for js promises to be resolved from rust code. Is it possible?

    If these features are on the roadmap, please let me know.

    async enhancement 
    Reply
  • idea: support writing Github action
    idea: support writing Github action

    Jun 9, 2020

                                                                                                                                                                                                            enhancement idea 
    Reply
  • idea: support writing VS Code extension
    idea: support writing VS Code extension

    Jun 9, 2020

                                                                                                                                                                                                            enhancement idea 
    Reply
  • idea: support writing extension for  electron
    idea: support writing extension for electron

    Jun 9, 2020

                                                                                                                                                                                                            enhancement idea 
    Reply
  • Fix linking problems on Windows
    Fix linking problems on Windows

    Apr 20, 2020

    I've fixed linking problems on Windows and most examples work correctly. That said, the ones that use async functions fail. For example, the "class" example fails with:

    obj.plusTwo(10).then( (val) => {
        ^
    
    TypeError: napi call failed InvalidArg
        at Object.<anonymous> (C:\Users\Freddy\Documents\WiseTime\node-bindgen\examples\class\test.js:49:5)
        at Module._compile (internal/modules/cjs/loader.js:689:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
        at Module.load (internal/modules/cjs/loader.js:599:32)
        at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
        at Function.Module._load (internal/modules/cjs/loader.js:530:3)
        at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
        at startup (internal/bootstrap/node.js:283:19)
        at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)
    
    Reply
  • Build fixes
    Build fixes

    Apr 3, 2020

    ~~Primarily this enables Travis builds on a combination of Rust versions, Node.js versions and Linux + OSX. Along the way,~~ I've found/fixed a few issues

    • #6 seems to have actually broken OSX support. I've fixed the fix, and also fixed a related item in nj-cli that made it not work on non-Macs
    • There was a couple of usages of Rust-style {} format specifiers in the Node code, which I've replaced with %s
    • Streams example had a #![feature(prelude_import)] which a) only worked on nightly Rust builds b) didn't appear to be needed anymore, so I've removed it. The streams test sometimes wasn't waiting long enough, so I've made it wait a bit longer.

    ~~There's a couple of commented out builds in the .travis.yml which don't fully work yet. I suspect they're because the checked in bindgen file in nj-sys needs regenerating on build, but I haven't fixed that yet.~~

    ~~There's also a build badge. It currently points to my repo, and you'll need to activate this repository in Travis and then switch over the URLs. See https://travis-ci.com/github/palfrey/node-bindgen/builds/158166702 for currently working build on my repo.~~

    Reply
  • Build CI pipeline
    Build CI pipeline

    Apr 3, 2020

    First decided on GitHub Action or Travis.

    Ability to build and test for following configuration:

    • OS: Mac, Windows, Linux
    • Node: Current version and LTS

    Other considerations:

    • Flexible
    • Easier to extend
    • Performant
    CI 
    Reply
  • Async/promise is not working on Windows.
    Async/promise is not working on Windows.

    Apr 24, 2020

    Hi, After updating to the new version, the compilation process was flawless also on Windows. That said, async/promise examples fail (whereas the others work correctly).

    Steps to reproduce

    1. Navigate to the examples/promise folder
    2. Run nj-cli build
    3. Execute node test.js

    The following error is reported:

    addon.hello(5).then((val) => {
          ^
    
    TypeError: napi call failed InvalidArg
        at Object.<anonymous> (C:\Users\Freddy\Documents\WiseTime\node-bindgen\examples\promise\test.js:5:7)
        at Module._compile (internal/modules/cjs/loader.js:689:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
        at Module.load (internal/modules/cjs/loader.js:599:32)
        at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
        at Function.Module._load (internal/modules/cjs/loader.js:530:3)
        at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
        at startup (internal/bootstrap/node.js:283:19)
        at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)
    

    Thank you for your work, let me know if you need help to test something on Windows

    Reply
  • Make it possible to regenerate bindgen work
    Make it possible to regenerate bindgen work

    Apr 4, 2020

    Currently this is hardcoded as https://github.com/infinyon/node-bindgen/blob/master/nj-sys/src/binding.rs with no docs/build.rs to redo it. It either needs some instructions on how to regenerate it, or ideally it gets rebuilt at build time.

    Reply
  • Request: Windows support
    Request: Windows support

    Apr 1, 2020

    This project is really excellent✨ But, in now, it would not work in Windows environment. I'm looking forward to support Windows.

    For now, I got these errors using nj-cli build in Windows:

    PS C:\Users\usagi\tmp\node-bindgen\examples\function> nj-cli build
       Compiling flv-future-aio v1.0.0
    error[E0433]: failed to resolve: could not find `unix` in `os`
     --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\fs\file_slice.rs:3:14
      |
    3 | use std::os::unix::io::RawFd;
      |              ^^^^ could not find `unix` in `os`
    
    error[E0433]: failed to resolve: could not find `sys` in `nix`
     --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\zero_copy.rs:7:10
      |
    7 | use nix::sys::sendfile::sendfile;
      |          ^^^ could not find `sys` in `nix`
    
    error[E0432]: unresolved import `nix::Error`
     --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\zero_copy.rs:8:5
      |
    8 | use nix::Error as NixError;
      |     ^^^^^^^^^^^^^^^^^^^^^^ no `Error` in the root
    
    error[E0412]: cannot find type `RawFd` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\fs\file_slice.rs:11:9
       |
    11 |     fd: RawFd,
       |         ^^^^^ not found in this scope
    
    error[E0412]: cannot find type `RawFd` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\fs\file_slice.rs:18:20
       |
    18 |     pub fn new(fd: RawFd,position: u64,len: u64) -> Self {
       |                    ^^^^^ not found in this scope
    
    error[E0412]: cannot find type `RawFd` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\fs\file_slice.rs:34:25
       |
    34 |     pub fn fd(&self) -> RawFd {
       |                         ^^^^^ not found in this scope
    
    error[E0405]: cannot find trait `AsRawFd` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\fs\file_slice.rs:42:6
       |
    42 | impl AsRawFd for AsyncFileSlice {
       |      ^^^^^^^ not found in this scope
    
    error[E0412]: cannot find type `RawFd` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\fs\file_slice.rs:44:28
       |
    44 |     fn as_raw_fd(&self) -> RawFd {
       |                            ^^^^^ not found in this scope
    
    error[E0405]: cannot find trait `AsRawFd` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\zero_copy.rs:46:26
       |
    46 | pub trait ZeroCopyWrite: AsRawFd {
       |                          ^^^^^^^ not found in this scope
    
    error[E0425]: cannot find value `ft` in this scope
      --> C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\flv-future-aio-1.0.0\src\zero_copy.rs:93:9
       |
    93 |         ft.await
       |         ^^ not found in this scope
    
    error: aborting due to 10 previous errors
    
    Some errors have detailed explanations: E0405, E0412, E0425, E0432, E0433.
    For more information about an error, try `rustc --explain E0405`.
    error: could not compile `flv-future-aio`.
    
    To learn more, run the command again with --verbose.
    thread 'main' panicked at 'copy failed: Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." }', C:\Users\usagi\.cargo\registry\src\github.com-1ecc6299db9ec823\nj-cli-0.1.2\src\main.rs:69:13
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    

    Please let me know if you know a workaround. I'm not hurry but I really expecting to success of the project. ?

    Installation Windows 
    Reply