Інтеграція Rust-коду в мобільний додаток через FFI
Rust у мобільних проектах з'являється у конкретних сценаріях: криптографічна логіка, парсинг бінарних протоколів, високонавантажена обробка даних, алгоритми де потрібна гарантія memory safety без GC-пауз. Discord використовує Rust для мобільного клієнта (синхронізація стану), Signal — для криптографії (libsignal написан на Rust та підключається до iOS та Android через FFI).
Як влаштована інтеграція
Rust компілюється у статичну бібліотеку (.a на iOS, .a/.so на Android) з C ABI через extern "C". На стороні мобільного додатку біндинги генеруються автоматично або пишуться вручну.
Cargo конфігурація. Cargo.toml:
[lib]
crate-type = ["staticlib", "cdylib"]
staticlib — для iOS (статичне пов'язування), cdylib — для Android (динамічна .so). Крос-компіляція через cargo-ndk (Android) та стандартний Cargo з iOS targets.
Targets для Android:
cargo ndk -t arm64-v8a -t x86_64 -o ./jniLibs build --release
Targets для iOS:
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
Потім xcodebuild -create-xcframework об'єднує варіанти device та simulator.
Генерація біндингів: uniffi проти cbindgen
mozilla/uniffi-rs — рекомендуємо для більшості проектів. Rust-інтерфейс описується у .udl файлі (UDL — Universal Definition Language), uniffi-bindgen генерує Kotlin-класи для Android та Swift-файли для iOS. Результат — нативне API без ручного написання JNI або Objective-C.
// mylib.udl
namespace mylib {
sequence<u8> encrypt(sequence<u8> data, string key);
};
Генерує mylib.kt з fun encrypt(data: List<UByte>, key: String): List<UByte> та mylib.swift з func encrypt(data: [UInt8], key: String) -> [UInt8].
cbindgen — генерує C-заголовочний файл з Rust. Підходить якщо потрібен тонкий C-шар, а біндинги до Swift/Kotlin пишуться вручну або через інший інструмент. Більше контролю, більше ручної роботи.
Потокобезпека та управління пам'яттю
На межі Rust↔мобільний потрібно явно управляти часом життя об'єктів. Якщо Rust-функція повертає указник на heap-аллоцировану структуру — мобільний код отримує Long (Android) або UnsafeRawPointer (iOS). Знищення об'єкту — через явний free_object(ptr) на Rust-стороні, викликаний з finalize()/deinit. Забути викликати free_* — витік пам'яті. Uniffi автоматизує це через Arc reference counting.
Rust паніка (panic!) через FFI — undefined behavior. Весь FFI-код обертаємо в std::panic::catch_unwind або використовуємо #[no_panic] аннотацій для критичних шляхів.
Async Rust у FFI. Можна створити tokio runtime всередину Rust-коду: Runtime::new().unwrap().block_on(async { ... }). Синхронно з точки зору FFI, але асинхронно всередину Rust. Для справжної async взаємодії — callback-based API або uniffi-rs з підтримкою async (експериментально у uniffi 0.25+).
Приклад. Месенджер з end-to-end шифруванням: криптографічне ядро на Rust (Double Ratchet, X3DH key exchange) через uniffi. Android: Kotlin викликає RatchetSession.encrypt(plaintext) — під капотом FFI до Rust. iOS: Swift викликає RatchetSession.encrypt(plaintext:). Один Rust-код — ідентична логіка на обох платформах. Unit-тести — на Rust (cargo test), інтеграційні тести — на Kotlin та Swift. CI: GitHub Actions, матриця з 4 targets, збірка xcframework та .aar як артефакти.
Налагодження та профілювання
Rust-код у мобільному додатку важче налагоджувати ніж нативний: LLDB підключається до процесу, символи завантажуються з .dSYM (iOS) або .so з debug info (Android). cargo build без --release зберігає debug symbols. Firebase Crashlytics показує стек через Rust-фрейм якщо символізація налаштована.
AddressSanitizer для Rust через RUSTFLAGS="-Z sanitizer=address" — знаходить use-after-free та buffer overflows до production.
Час реалізації
| Тип інтеграції | Орієнтовні строки |
|---|---|
| Простата функція через cbindgen (один алгоритм) | 2–3 тижні |
| Бібліотека зі станом через uniffi | 4–8 тижнів |
| Повнофункціональне криптографічне ядро | 2–5 місяців |
Ціна розраховується індивідуально. Ключові фактори: складність Rust API, вимоги до продуктивності, потреба у підтримці обох платформ.







