---
title: "Rust N-API Modules for React Native's New Architecture"
published: true
description: "Build a memory-safe native module using Rust compiled to N-API for React Native's Bridgeless mode, with zero-copy ArrayBuffer transfers and simplified cross-platform builds."
tags: rust, mobile, android, architecture
canonical_url: https://mvpfactory.co/blog/rust-napi-modules-react-native-new-architecture
---
## What We Will Build
In this workshop, we are replacing a JSI C++ bridge with a Rust-based N-API module in React Native's Bridgeless mode. By the end, you will have a working native module that processes image buffers with zero-copy transfers between Hermes and Rust — no custom JSI C++ glue required.
Let me show you a pattern I use in every project that ships compute-heavy native code. It cuts native crashes, simplifies your build pipeline, and gives you memory safety guarantees at the FFI boundary that C++ simply cannot match.
## Prerequisites
- React Native 0.74+ with Bridgeless mode enabled
- Rust toolchain installed (`rustup`)
- `cargo-ndk` (Android) and `cargo-lipo` (iOS) installed
- Familiarity with TurboModules and basic Rust syntax
## Step 1: Understand Why We Are Dropping C++
C++ native modules are the largest source of hard-to-diagnose crashes in complex React Native apps. Use-after-free, dangling pointers, buffer overflows at the FFI boundary — these account for a disproportionate share of native crash reports.
The new architecture's TurboModules and Bridgeless mode decoupled the native interface enough that we can slot in any language that compiles to a compatible ABI. Rust, via N-API bindings, fits perfectly.
| Bug class | C++ risk | Rust risk |
|---|---|---|
| Use-after-free | High (manual lifetime mgmt) | Eliminated (ownership system) |
| Buffer overflow | High (raw pointer arithmetic) | Eliminated (bounds checking) |
| Data races | High (shared mutable state) | Eliminated (Send/Sync traits) |
| Null pointer deref | Moderate | Eliminated (Option type) |
| Memory leaks | Moderate (RAII helps) | Low (RAII + no exceptions) |
I have seen a rewrite of a single image pipeline module cut native crashes by over 40% in one release cycle. Those are production numbers, not benchmarks.
## Step 2: Write Your Rust N-API Module
Rust compiles to a shared library (`.so` on Android, `.a` on iOS) exposing N-API-compatible functions. Hermes calls into it through the standard N-API interface.
Here is the minimal setup to get this working:
rust
use napi_derive::napi;
use napi::bindgen_prelude::*;
[napi]
pub fn process_image_buffer(input: Buffer) -> Result {
// Zero-copy: input references the JS ArrayBuffer directly
let pixels = input.as_ref();
let output = apply_pipeline(pixels)?;
Ok(Buffer::from(output))
}
The critical detail is the `Buffer` type. N-API's `Buffer` maps directly to a JavaScript `ArrayBuffer`, and when Hermes passes one across the boundary, no data is copied. For image processing workloads, this is the difference between viable and not.
## Step 3: Design for Zero-Copy Transfers
The FFI boundary design matters enormously. The pattern: Hermes allocates the `ArrayBuffer`, N-API passes a pointer to Rust (zero-copy), Rust processes in-place or allocates a new output buffer, then returns ownership back to Hermes.
rust
[napi]
pub fn transform_in_place(mut buffer: Buffer) -> Result<()> {
let data = buffer.as_mut();
// Modify pixels directly, no allocation, no copy
for pixel in data.chunks_exact_mut(4) {
pixel[3] = 255; // Set alpha to opaque
}
Ok(())
}
This keeps memory allocation on one side of the boundary, avoiding the double-allocation trap that plagues many C++ bridge implementations.
## Step 4: Wire Up Cross-Platform Builds
Here is the gotcha that will save you hours. The build system is where C++ native modules truly fall apart. Rust's toolchain cuts through most of it.
| Concern | C++ (CMake/ndk-build) | Rust (cargo-ndk / cargo-lipo) |
|---|---|---|
| Android cross-compilation | Manual NDK toolchain files | `cargo ndk -t arm64-v8a build --release` |
| iOS fat binary | Xcode build phases + lipo scripts | `cargo lipo --release` |
| Dependency management | Conan/vcpkg or vendored headers | `Cargo.toml` with crates.io |
| Reproducible builds | Fragile, CI-specific scripts | `cargo` lockfile, deterministic |
| New developer setup | 30-60 min toolchain config | `rustup target add` + build |
Android integration hooks into Gradle via a custom task invoking `cargo ndk`. iOS hooks into the Xcode build phase calling `cargo lipo`. That is it.
## Gotchas
- **Do not serialize at the bridge.** Do not convert data to JSON or base64 at the FFI boundary. N-API's `Buffer` gives you direct memory access from Hermes, and Rust's borrow checker ensures you use it safely.
- **This is not for every module.** If your native module is a thin wrapper around a platform SDK (HealthKit, Camera APIs), stay with Objective-C or Kotlin. Rust N-API modules make sense for compute-heavy, cross-platform logic: image processing, cryptography, data transformation, compression.
- **Start small.** Pick your most crash-prone C++ native module, rewrite it in Rust with N-API bindings, and measure crash rate reduction over a release cycle. Concrete numbers make the case for broader adoption better than any architecture document will.
## Wrapping Up
Rust-based N-API modules are the single best improvement you can make to a native module pipeline right now. You get memory safety guarantees at the FFI boundary, zero-copy ArrayBuffer transfers to Hermes, and a build story that reduces CI configuration to a few lines. Start with one module, measure the crash reduction, and let the numbers speak for themselves.
Top comments (0)