Rust vs Kotlin: Where Systems Logic Meets Managed Applications
Rust belongs at the systems boundary: performance-sensitive libraries, command-line tools, networking, storage, embedded code, and components that must avoid garbage collection. Kotlin belongs at the managed application boundary: Android UI, JVM services, business workflows, and integration with Java libraries. Use both only when a measured boundary justifies the FFI or service complexity.
Ownership and garbage collection move costs to different places
Rust's ownership and borrowing rules reject many lifetime, aliasing, and cross-thread mistakes at compile
time without a tracing garbage collector. The
Rust ownership chapter
explains the model. This does not make every Rust program safe: unsafe code, FFI, logic bugs,
resource exhaustion, and panics remain possible.
Kotlin/JVM uses managed memory and benefits from decades of JVM tooling, libraries, profilers, and garbage collectors. That often makes business applications faster to develop and operate. Garbage-collection pauses and allocation rates are workload- and collector-specific; they should be measured rather than described as an inherent latency failure. Kotlin/Native has a different runtime and interoperability profile from Kotlin/JVM.
Prefer a process boundary before FFI
A versioned network or command-line boundary isolates crashes, deploys independently, and works across architectures. It is often the conservative way to put a Rust engine behind a Kotlin service. Define schemas, timeouts, cancellation, backpressure, error codes, compatibility, and observability before optimizing transport.
FFI can be appropriate for offline-first mobile logic, cryptography, codecs, or a hot local algorithm. Rust's FFI guidance and Android's JNI recommendations show why the boundary must be small. Specify who allocates and frees memory, how strings and buffers are encoded, which thread may call back, and how panics and exceptions are translated.
Choose with a prototype and failure test
Implement the riskiest path first and measure latency distribution, throughput, memory, startup, binary size, build time, battery use where relevant, and debugging across the boundary. Test cancellation during a call, corrupt input, a Rust panic, a Kotlin exception, version mismatch, and process restart. Keep a pure-language reference test for serialized data.
Choose Kotlin alone when the application is dominated by Android or JVM integration and its performance target is already met. Choose Rust alone for a standalone systems component with a clear ownership model. Combine them when shared or performance-critical logic has demonstrated value greater than the boundary's maintenance cost. Rust's unsafe Rust chapter is a reminder that every escape hatch needs a documented invariant and focused tests.
Published · Updated