Rust is the most loved language right now — sounds like a cliche already 😃, but it’s true. In fact, it has been added to the Linux Kernel and going to be added to the Windows Kernel.
Not every language enjoys that kind of privilege and love, you know. One of the benefits of Rust is that it’s fast, if you have implemented something awesome in Rust and you want to use it in your NodeJS project to enjoy the benefits of Rust, it’s possible. You can compile your rust code as a dynamic library and use it in your NodeJS application. I’ll show you how you can do that in this article.
Note: In this article, I’ll assume you are familiar with Rust and Javascript on a very basic level
So, let’s start by writing a basic Rust addition function and release it as a library.
Run cargo init --lib
to initialize a brand new Rust library project. Replace the content of the lib.rs
file that will be generated with the code below:
#[no_mangle]
pub fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
Now, let me explain the code. This is a simple function that adds two integers. But what about the #[no_mangle]
, you asked?
In compiler construction, name mangling (also called name decoration) is a technique used to solve various problems caused by the need to resolve unique names for programming entities in many modern programming languages. It provides a way of encoding additional information in the name of a function, structure, class or another datatype in order to pass more semantic information from the compiler to the linker.
Wikipedia
In our case, we don’t want the compiler to tamper with the name we want to be able to access the function name as it’s since we need to use it in external applications or programming languages.
So, with Rust, you can decorate your function with the #[no_mangle]
attribute and the compiler will leave your function name as it is.
We have the rust code, now, let’s compile it. But wait before we do that, replace your Cargo.toml
file with the code below, so that we’ll be on the same page:
[package]
name = "addition_library"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name="addition_library"
path = "src/lib.rs"
crate-type = ["dylib"]
The crate-type = ["dylib"]
is a configuration option that specifies that a Rust crate should be compiled as a dynamic library, also known as a shared library. It will generate the library with a file extension of .so
on Linux, .dll
on Windows, or .dylib
on macOS
So, now that we’ve completed this, let’s compile it and use it in our Node.js app.
Run the command below to create a new build for the library.
cargo build --release
Your file structure should now look like this:
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── target
├── CACHEDIR.TAG
└── release
├── build
├── libaddition_library.dylib
Since I am on a MacOS, I am interested in the libaddition_library.dylib
file. That’s what I’ll import into my NodeJs project.
Now, create a NodeJS project. It does not have to be in the same directory. In fact, I will advise that you create it in a different folder for the sake of this tutorial.
In the directory run npm init -y
to initialize a new project with npm and then install the Node FFI library by running npm i ffi-napi
. The node-ffi-napi
package allows you to load and call dynamic libraries from your Javascript application.
Once the installation is complete, create a directly rustlib
(you can call it whatever you want), copy the libaddition_library.dylib
file into it.
Now, create an app.js
file and add the following content to it:
const ffi = require("ffi-napi");
// Load the dynamic library
const lib = ffi.Library("rustlib/libaddition_library.dylib", {
add_numbers: ["int", ["int", "int"]],
});
// Call the add_numbers function
const result = lib.add_numbers(2, 3);
console.log(result);
So far our file structure for the Node.js app should look like this:
.
├── app.js
├── package-lock.json
├── package.json
└── rustlib
└── libaddition_library.dylib
So, let’s take a closer look at the code:
const lib = ffi.Library("rustlib/libaddition_library.dylib", {...});
This line loads the dynamic library file we generated from our Rust build. Theffi.Library
method takes two arguments: the path to the library file, and an object that defines the functions in the library that can be called from JavaScript.add_numbers: ["int", ["int", "int"]]
This line defines a function calledadd_numbers
in the loaded library. The function takes twoint
parameters and returns anint
. The first element of the array ("int"
) specifies the return type of the function, and the second element of the array (["int", "int"]
) specifies the types of the function’s two parameters.
If we had more functions we wanted to call from the library we could also specify them in the second parameter.
And finally, we can call the function and pass the required parameters.
const result = lib.add_numbers(5, 8);
console.log(result);
So, let’s try running the code with node app.js
And there you have it. We have written Rust and imported it into our Node.Js application.
Please share with your audience if you enjoyed reading this. Follow me on Twitter
If you have questions, please, ask in the comment.
This is truly remarkable stuff. Honestly, I am glad I finally chosen to learn Rust programming language
Yea, I agree with you. Rust is a fine language.