Ibiyemi Abiodun
« main pageOctober 2022 to August 2023
In winter 2022, I created jsi-rs
as part of another project. I was building
implement a custom Vulkan renderer for advanced graphics inside of a React
Native app, and I wanted to take advantage of wgpu
and my existing background
in Rust rather than write it in C++.
In React Native, the best way to interoperate between native code and JavaScript code is currently JSI (JavaScript Interfaces). It gives your native code a direct reference to the JavaScript runtime (and vice versa) that eliminates the slow marshalling that took place with older React Native APIs.
jsi-rs
is a library that makes it possible to write these JSI modules in Rust
and take advantage of all of the benefits that Rust brings, including a simpler
type system, a more consistent build system, a robust package manager, and
excellent cross-platform portability.
An example
Here are the steps that I took to create a simple JSI module using jsi-rs
,
referencing the code here:
-
Create a blank React Native project:
npx react-native@latest init example
-
Create a blank Cargo project inside:
cd example && cargo init --lib example-jsi-module
-
Add
jsi
,jni
, andcxx
as dependencies ofexample-jsi-module
jsi
will allow us to create a React Native modulejni
is needed for interop with Java code on Androidcxx
is needed to initializejsi
because JSI is implemented in C++ with smart pointers
-
Write the code in
example-jsi-module/src
android.rs
: provides an entrypoint to our Rust library that can be called from Javalib.rs
: the general entrypoint of our library that is shared between Android and iOS
-
Update
android/build.gradle
to add the following plugin underbuildscript.dependencies
:buildscript { repositories { // don't remove the existing repositories, just add maven b/c the Rust plugin is hosted there maven { url "https://plugins.gradle.org/m2/" } } dependencies { // don't remove the existing dependencies, just add this one classpath("io.github.MatrixDev.android-rust:plugin:0.3.2") } }
This plugin will compile
example-jsi-module
using the Android NDK as part of building our application for Android -
Make sure the Android NDK is installed. Version 23 should work
-
Update
android/app/build.gradle
to add the following lines:apply plugin: "io.github.MatrixDev.android-rust" androidRust { module("example-jsi-module") { it.path = file("../example-jsi-module") // default abi targets are arm and arm64; if you want to run on Android // Emulator then you may need to add x86_64 // targets = ["arm", "arm64", "x86", "x86_64"] } }
Note: when you compile the program, this Gradle plugin will install all of the Rust Android targets if they are not already installed
-
Write the code in
android/app/src/main/java/com/example/ExampleJsiModule.java
. This will be called when the application starts, and it gives us a pointer to the React Native runtime so we can initialize our Rust code -
Write the code in
android/app/src/main/java/com/example/ExampleJsiPackage.java
. This lets React Native discover our module -
In
android/app/src/main/java/com/example/MainApplication.java
, add the following line togetPackages()
:packages.add(new ExampleJsiPackage());
-
Add a couple of lines to
App.tsx
to call our native module (I added them near the top):// call our Rust module const {ExampleJsiModule} = NativeModules; ExampleJsiModule.install(); // call our global object injected by Rust console.log('the current time is: ' + ExampleGlobal.time());
-
Connect your Android device or start an emulator
-
Run
npm run start
and press A to deploy to Android -
You should see
hello from Rust
in the terminal after the app loads!
The potential
In this example, I added an object to the JavaScript global namespace. This object is a host object, which is special because when you interact with it from JavaScript, you are calling directly into your Rust code! The functions and properties on host objects can return any JS value including other host objects, allowing you to construct any API you can imagine with minimal overhead.
In my other project, I used them to create a custom React Native control that
renders a scene with hardware acceleration using wgpu
.