The Case for a Neutral Shared Core
Kotlin Multiplatform (KMP) often fails in cross-platform teams because it imposes a "source of truth" language (Kotlin) on iOS engineers, resulting in non-idiomatic Swift wrappers and complex build-time friction. The "Headless Mobile Architecture" replaces this with a neutral Rust core. Because Rust is not the primary language for Android, iOS, or Web teams, it acts as a neutral territory where no single platform team is forced to adapt to another's toolchain.
Technical Implementation with UniFFI
The architecture relies on a Cargo workspace that separates business logic from platform-specific bindings. The core logic is written in pure Rust, while UniFFI acts as the bridge to generate native interfaces:
- Contract-First: A
.udlfile defines the interface, which UniFFI uses to generate idiomaticsuspendfunctions for Kotlin andasync throwsfor Swift. - Zero-Cost Abstractions: Rust provides memory safety without a garbage collector, ensuring deterministic resource deallocation and avoiding the GC pauses that can impact UI performance on mobile.
- Platform-Specific Bindings: Android consumes the Rust core via JNI/Kotlin, iOS via Swift Package Manager/XCFramework, and Web via
wasm-bindgen. Each platform receives a native-feeling API without needing to write manual adapter code.
Managing Async and Memory
When bridging async runtimes, developers must handle potential leaks. For example, if a Kotlin coroutine cancels, the underlying Rust future must be explicitly aborted to prevent "zombie" network requests. This is solved by implementing a Drop trait on an AbortHandle that triggers when the future is cancelled. Additionally, a global Tokio runtime must be initialized at process start to bridge the gap between mobile async runtimes and Rust's task execution.
Trade-offs and Reality Check
While this architecture provides a single source of truth and consistent behavior across platforms, it introduces specific costs:
- Learning Curve: The team must maintain a Rust codebase, which requires understanding lifetimes and strict compiler rules.
- Debugging Complexity: Native crashes occurring within the Rust layer are harder to trace than standard JVM/Swift exceptions, often requiring specialized tooling.
- Build Infrastructure: Setting up cross-compilation for Android (NDK) and iOS (XCFramework) requires significant initial investment—budget at least one week for the initial pipeline setup.
Ultimately, this approach is recommended for teams where all three platforms are first-class citizens and consistency in business logic (e.g., security, encryption, or complex state management) is non-negotiable.