Use Rust in React Native through WebAssembly
This post shows how to use Rust functions in a React Native project through WebAssembly. I struggled quite a bit to find an easy way to make it work, so I thought I’d share my findings. You can find the code for this post here.
Here’s the TLDR:
- Create a
wasm-pack
project exposing the Rust functions you want to export. - Serve a web page exposing these functions through
message
events. - Use a React Native
WebView
of this web page. - Call Rust functions by sending messages to the
WebView
.
Pros and Cons
Pros
- One alternative is to use native modules but it is a pain to setup with Rust and React Native, and requires lots of different configurations for Android and iOS.
- Another alternative is to serve
wasm
files locally on the app and use them in aWebView
. This is also a pain to setup since local files need to be put in different places in Android and iOS, and you will get a bunch of permission errors along the way. - The solution shown here is very easy to setup, works out of the box, and allows for a clear separation between the Rust and React Native development.
Cons
- The device needs an Internet connection to download the web page.
- Mild privacy issue since one can track requests to the web server. But all computations using WebAssembly are done locally on the device. In particular, the parameters to the Rust functions will not be sent to the server.
- Probably quite slower than using the native modules.
Prototype
This gives an example of this structure with a simple Rust function that adds two numbers.
Rust
Create a wasm-pack
project following the instructions given here. The src/lib.rs
exposes the functions you want to export. Here’s ours:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
a + b
}
Then, build the package with wasm-pack build
and create a web directory with package.json
like:
/* -------- SNIP -------- */
"devDependencies": {
"rust": "file:../pkg",
"webpack": "^4.29.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5",
"copy-webpack-plugin": "^5.0.0"
}
/* -------- SNIP -------- */
The WebAssembly functions can be called from Javascript using message
events. Here’s our index.js
:
import * as wasm from "rust"; // Import the wasm package
let sum;
document.addEventListener("message", function(event) { // Receive parameters in a message
let {a: s1, b: s2} = JSON.parse(event.data); // Parse the parameters
sum = wasm.add(s1, s2); // Call the wasm function
window.ReactNativeWebView.postMessage(JSON.stringify({sum})); // Send a message to React Native with the result of the wasm function.
}, false);
Now, serve this web directory on a web server. For this prototype, we serve it on localhost:8080
.
React Native
Create a React Native project with react-native-webview
linked. Here is our App.js
that shows the basic way to call the WebAssembly functions served by our web server:
import React, { Component } from 'react';
import { View, Button, TextInput, Text } from 'react-native';
import { WebView } from 'react-native-webview';
export default class App extends Component {
webView = null; // Holds the reference to the WebView
state = { s1: null, s2: null, sum: null };
// Sends a message to the WebView with the parameters of the Rust function `add`.
sendMessage = () => {
this.webView.postMessage(JSON.stringify({ a: parseInt(this.state.s1), b: parseInt(this.state.s2) }));
}
// Listen for messages from the WebView containing the result of the wasm function.
onMessage = (event) => {
this.setState({sum: JSON.parse(event.nativeEvent.data).sum}, () => console.log(this.state));
}
render() {
return (
<View style={{ flex: 1 }}>
<Button
title="Press me"
onPress={this.sendMessage} // Sends a message on button press.
/>
<TextInput
placeholder="First summand"
onChangeText={(text) => this.setState({ s1: text })}
value={this.state.s1}
/>
<TextInput
placeholder="Second summand"
onChangeText={(text) => this.setState({ s2: text })}
value={this.state.s2}
/>
<WebView
style={{ height: 0 }}
useWebkit={true}
originWhitelist={['*']}
javaScriptEnabled={true}
source={{ uri: 'http://10.0.2.2:8080/' }} // Change to your webserver.
allowFileAccess={true}
cacheEnabled={false}
ref={(webView) => this.webView = webView} // Set ref.
onMessage={this.onMessage} // Listens for messages.
/>
{this.state.sum &&
<Text>The sum is {this.state.sum}</Text>}
</View>
)
}
}
This component gives a way to call the Rust function add
from React Native, through WebAssembly.
Conclusion
Although this solution may seem a bit hacky, I found that it works well for my needs and gives me the least complexity to use the speed and safety of Rust and WebAssembly in my React Native projects.
If you find a problem in the code or a better way to achieve this, don’t hesitate to create an issue on the repository.