Rust Code Integration in Mobile Apps via FFI
Rust appears in mobile projects for specific scenarios: cryptographic logic, binary protocol parsing, data-heavy processing, algorithms needing memory safety guarantee without GC pauses. Discord uses Rust for mobile client (state sync), Signal for cryptography (libsignal in Rust, connected to iOS and Android via FFI).
How Integration Works
Rust compiles to static library (.a on iOS, .a/.so on Android) with C ABI via extern "C". Mobile side: bindings auto-generated or hand-written.
Cargo configuration. Cargo.toml:
[lib]
crate-type = ["staticlib", "cdylib"]
staticlib for iOS (static linking), cdylib for Android (dynamic .so). Cross-compile via cargo-ndk (Android) and standard Cargo with iOS targets.
Android targets:
cargo ndk -t arm64-v8a -t x86_64 -o ./jniLibs build --release
iOS targets:
cargo build --target aarch64-apple-ios --release
cargo build --target aarch64-apple-ios-sim --release
cargo build --target x86_64-apple-ios --release
lipo -create target/x86_64-apple-ios/release/libmylib.a \
target/aarch64-apple-ios-sim/release/libmylib.a \
-output libmylib-sim.a
Then xcodebuild -create-xcframework combines device and simulator variants.
Binding Generation: uniffi vs cbindgen
mozilla/uniffi-rs—recommended for most projects. Rust interface described in .udl file (UDL—Universal Definition Language), uniffi-bindgen generates Kotlin classes for Android and Swift files for iOS. Result: native API without manual JNI or Objective-C.
// mylib.udl
namespace mylib {
sequence<u8> encrypt(sequence<u8> data, string key);
};
Generates mylib.kt with fun encrypt(data: List<UByte>, key: String): List<UByte> and mylib.swift with func encrypt(data: [UInt8], key: String) -> [UInt8].
cbindgen—generates C header from Rust. Fits when thin C layer needed, bindings to Swift/Kotlin written manually or via other tool. More control, more manual work.
Thread Safety and Memory Management
At Rust↔mobile boundary, explicitly manage object lifetimes. Rust function returning heap-allocated structure pointer—mobile code gets Long (Android) or UnsafeRawPointer (iOS). Object destruction via explicit free_object(ptr) on Rust side, called from finalize()/deinit. Forgetting free_* = memory leak. Uniffi automates via Arc reference counting.
Rust panic! through FFI—undefined behavior. Wrap all FFI code in std::panic::catch_unwind or use #[no_panic] annotations for critical paths.
Async Rust in FFI. Create tokio runtime inside Rust: Runtime::new().unwrap().block_on(async { ... }). Synchronous from FFI perspective, async inside Rust. For true async interaction: callback-based API or uniffi-rs with async support (experimental in uniffi 0.25+).
Case study. End-to-end encrypted messenger: cryptographic core in Rust (Double Ratchet, X3DH key exchange) via uniffi. Android: Kotlin calls RatchetSession.encrypt(plaintext)—FFI to Rust under hood. iOS: Swift calls RatchetSession.encrypt(plaintext:). Single Rust code—identical logic both platforms. Unit tests in Rust (cargo test), integration tests in Kotlin and Swift. CI: GitHub Actions, matrix of 4 targets, build xcframework and .aar as artifacts.
Debugging and Profiling
Rust code in mobile harder to debug than native: LLDB connects to process, symbols loaded from .dSYM (iOS) or .so with debug info (Android). cargo build without --release preserves debug symbols. Firebase Crashlytics shows stack through Rust frame if symbolization configured.
AddressSanitizer for Rust via RUSTFLAGS="-Z sanitizer=address"—finds use-after-free and buffer overflows before production.
Timeline
| Integration Type | Estimated Timeline |
|---|---|
| Simple function via cbindgen (single algorithm) | 2–3 weeks |
| Library with state via uniffi | 4–8 weeks |
| Full cryptographic core | 2–5 months |
Pricing determined individually. Key factors: Rust API complexity, performance requirements, both-platform support need.







