Kotlin/Rust Interoperability (UniFFI)
Relevant source files
The following files were used as context for generating this wiki page:
- rust/hesabyar-core/src/ffi/mod.rs
- app/src/main/java/io/github/mojri/hesabyar/rust/RustBridge.kt
- app/src/main/java/io/github/mojri/hesabyar/rust/RustMappers.kt
- rust/uniffi/hesabyar_core/uniffi/hesabyar_core/hesabyar_core.kt
- rust/hesabyar-core/README.md
- app/src/test/java/io/github/mojri/hesabyar/rust/RustBridgeTest.kt
- rust/hesabyar-core/src/ai_validation.rs
Introduction
The Hesabyar project utilizes UniFFI (Universal Foreign Function Interface) to bridge high-performance business logic written in Rust with the Android application's Kotlin codebase. This architecture allows the project to maintain a single, cross-platform core (hesabyar-core) for critical financial calculations, Persian NLP parsing, Jalali calendar conversions, and data validation while ensuring memory safety and native performance.
By using UniFFI, the project avoids manual JNI (Java Native Interface) boilerplate. Instead, it relies on auto-generated scaffolding and Kotlin bindings that facilitate the transfer of complex data structures (records, enums, and lists) across the FFI boundary using efficient serialization into RustBuffer objects.
Sources: rust/hesabyar-core/README.md:10-25, rust/hesabyar-core/README.md:95-105
Architectural Components
The interoperability layer consists of three main tiers: the Rust Implementation, the UniFFI Scaffolding, and the Kotlin Bridge.
1. Rust FFI Layer
The Rust core defines exported functions using the #[uniffi::export] macro. A dedicated FFI module handles safety concerns, such as catching panics to prevent them from crashing the JVM and initializing global hooks for logging.
Sources: rust/hesabyar-core/src/ffi/mod.rs:1-30
2. Auto-generated Scaffolding
The uniffi-gen tool processes the Rust code to produce:
- C-compatible headers: Low-level entry points for JNA.
- Kotlin Bindings: High-level classes and objects (e.g.,
HesabyarCore) that mirror the Rust API. - FfiConverters: Specialized objects that handle the "lifting" (FFI to Kotlin) and "lowering" (Kotlin to FFI) of data types.
Sources: rust/uniffi/hesabyar_core/uniffi/hesabyar_core/hesabyar_core.kt:130-170, rust/hesabyar-core/README.md:65-75
3. Kotlin Bridge and Mappers
The RustBridge object acts as the primary interface for the app, providing safe access to native functions with fallback values if the library fails to load. RustMappers performs the final translation between UniFFI-generated records and the application's domain/UI models.
Sources: app/src/main/java/io/github/mojri/hesabyar/rust/RustBridge.kt:20-50, app/src/main/java/io/github/mojri/hesabyar/rust/RustMappers.kt:20-40
Data Flow Logic
The system uses a synchronous/asynchronous split. Calculations like currency formatting or date conversion are performed synchronously on the calling thread, while complex operations like AI validation or database-heavy analytics are dispatched to Dispatchers.Default.
This diagram illustrates the full lifecycle of a cross-language function call from the UI to the Rust core and back.
Sources: app/src/main/java/io/github/mojri/hesabyar/rust/RustBridge.kt:35-60, rust/uniffi/hesabyar_core/uniffi/hesabyar_core/hesabyar_core.kt:165-195
Type Mapping and Serialization
UniFFI handles the conversion of basic and complex types through the FfiConverter interface.
| Kotlin Type | Rust Type | Conversion Mechanism |
|---|---|---|
Long | i64 | FfiConverterLong (Direct 8-byte copy) |
String | String | FfiConverterString (UTF-8 encoding into RustBuffer) |
ByteArray | Vec<u8> | FfiConverterByteArray (Length-prefixed buffer) |
Transaction | Record | Field-by-field serialization into ByteBuffer |
TransactionType | Enum | Integer-based ordinal mapping |
Sources: rust/uniffi/hesabyar_core/uniffi/hesabyar_core/hesabyar_core.kt:600-650, rust/uniffi/hesabyar_core/uniffi/hesabyar_core/hesabyar_core.kt:1150-1200
Safety and Exception Handling
Panic Management
Rust panics are fatal if they cross the FFI boundary. The project implements a catch_unwind_safe wrapper in the Rust FFI layer. If a panic occurs, it is converted into a HesabyarError::ParseError and thrown as a HesabyarException in Kotlin.
pub fn catch_unwind_safe<F, T>(f: F) -> Result<T, HesabyarError>
where
F: std::panic::UnwindSafe + std::panic::RefUnwindSafe + FnOnce() -> T,
{
ensure_initialized();
match std::panic::catch_unwind(f) {
Ok(result) => Ok(result),
Err(panic) => {
// ... conversion logic ...
Err(HesabyarError::ParseError { detail: msg })
}
}
}
Sources: rust/hesabyar-core/src/ffi/mod.rs:40-60
Availability Checks
RustBridge verifies the native library status before every call. If available is false, it immediately returns a hardcoded "safe sentinel" or null to prevent crashes.
Sources: app/src/main/java/io/github/mojri/hesabyar/rust/RustBridge.kt:25-45
Key Modules Exposed via FFI
The following table summarizes the primary business logic modules accessible through the UniFFI bridge:
| Module | Key Functions | Purpose |
|---|---|---|
| Calendar | gregorianToJalali, jalaliToGregorian | Precise Jalali/Gregorian conversions. |
| Parser | parseSentenceOffline, inferExpenseCategory | Persian NLP and category inference. |
| Validation | validateTransaction, validateBackupPayload | Cross-language entity integrity checks. |
| Analytics | computeAnalytics, computeDashboardData | Aggregating transaction data for UI. |
| AI Support | parseAiTransactionJson, validateAiAdvice | Sanitizing and repairing AI model outputs. |
Sources: rust/hesabyar-core/src/ffi/mod.rs:80-250, rust/hesabyar-core/src/ai_validation.rs:115-150
Implementation Example: AI Validation Flow
The AI validation flow demonstrates the complexity of the bridge, involving JSON parsing in Rust and metadata mapping in Kotlin.
Summary
Kotlin/Rust Interoperability via UniFFI is the backbone of Hesabyar's "Core Logic" strategy. By isolating complex Persian NLP and financial logic into a memory-safe Rust core and exposing it through typed Kotlin bindings, the project achieves a high degree of reliability and performance while maintaining the ease of development provided by Jetpack Compose on the frontend. Key safety measures like panic catching and synchronous fallback paths ensure that the user experience remains stable even if the native layer encounters unexpected states.
Sources: rust/hesabyar-core/README.md:95-105, app/src/test/java/io/github/mojri/hesabyar/rust/RustBridgeTest.kt:15-30