Skip to main content
CoreCrypto exposes a single Rust implementation through three language bindings. Each binding is generated automatically from the Rust source — UniFFI for Swift and Kotlin, and uniffi-bindgen-react-native/wasm-bindgen for TypeScript. Understanding how Rust types and identifiers are translated into each target language lets you navigate the generated APIs without reading the generated code directly.

Naming conventions

Identifiers are translated mechanically between Rust and each target language. The table below shows the convention applied to each identifier category.
ItemRustSwiftKotlinTypeScript
Methods / Functionssnake_casecamelCasecamelCasecamelCase
Variables / Argumentssnake_casecamelCasecamelCasecamelCase
Classes / InterfacesPascalCasePascalCasePascalCasePascalCase
For example, the Rust method mls_init becomes mlsInit in Swift, Kotlin, and TypeScript. The Rust struct CoreCryptoCallbacks remains CoreCryptoCallbacks in all target languages.

Type equivalence

The table below covers every primitive and composite Rust type exposed through the FFI. Types not listed here (custom structs, enums) are generated as equivalent classes or enums in the target language with the naming convention above applied.
RustSwiftKotlinTypeScript
boolBoolBooleanboolean
u8UInt8UBytenumber
u16UInt16UShortnumber
u32UInt32UIntnumber
u64UInt64ULongnumber
i8Int8Bytenumber
i16Int16Shortnumber
i32Int32Intnumber
i64Int64Longnumber
f32FloatFloatnumber
f64DoubleDoublenumber
String / &strStringStringstring
std::time::SystemTimeDatejava.time.InstantDate
std::time::DurationTimeIntervaljava.time.Durationnumber (milliseconds)
Option<T>Optional<T>Optional<T>T?
Vec<T>Array<T>List<T>Array<T>
HashMap<String, T>Dictionary<String, T>Map<String, T>Record<string, T>
()nilnullnull
Result<T, E>func f() throws E -> Tfun f(): T // throws Efunction f(): T // @throws E
TypeScript represents all integer and floating-point Rust types as number. There is no separate integer type in TypeScript. Values larger than Number.MAX_SAFE_INTEGER (i.e., u64/i64 near their limits) may lose precision.
std::time::Duration maps to a plain number in TypeScript representing milliseconds, not seconds.

UniFFI-generated bindings (Swift and Kotlin)

Swift and Kotlin bindings are auto-generated by UniFFI from the Rust source in crypto-ffi/. UniFFI reads the #[uniffi::export] annotations on Rust functions, traits, and structs, then emits matching Swift and Kotlin source files. The generation is driven by two configuration files:
  • crypto-ffi/uniffi.toml — used for Swift and JVM Kotlin. Sets the package name (com.wire.crypto), the shared library name (core_crypto_ffi), and a custom Timestamp type mapping to kotlinx.datetime.Instant.
  • crypto-ffi/uniffi-android.toml — used for Android Kotlin. Enables the Android-specific object cleaner and maps KotlinInstant to kotlinx.datetime.Instant.
Because the bindings mirror the Rust signatures directly, any function that takes a Vec<u8> in Rust will accept Data in Swift and ByteArray in Kotlin. No manual wrapping is required.

Async patterns

Rust async fn functions are translated as follows:
Rust async functions become native Swift async functions. Call them with await inside an async context or a Task.
try await coreCrypto.transaction { ctx in
    try await ctx.mlsInit(clientId: clientId, transport: transport)
}

wasm-bindgen / UBRN bindings (TypeScript)

TypeScript bindings for the browser target are generated from Rust code annotated with #[wasm_bindgen], compiled to wasm32-unknown-unknown, and processed with wasm-bindgen. The native (Node.js) target uses uniffi-bindgen-react-native (ubrn) to generate N-API bindings from the same UniFFI-annotated Rust source used for Swift and Kotlin. Both targets are configured via crypto-ffi/bindings/js/ubrn.config.yaml and produce a single @wireapp/core-crypto package with two entry points:
  • @wireapp/core-crypto/browser — WASM build for web browsers
  • @wireapp/core-crypto/native — N-API build for Node.js
The public TypeScript API is identical across both entry points.

Async patterns

All async Rust functions become Promise-returning TypeScript functions. Use await or .then() as normal.
await coreCrypto.newTransaction(async (ctx) => {
    await ctx.mlsInit(clientId, transport);
});

Error handling

Result<T, E> on the Rust side propagates as a thrown error in all three binding languages.
Failable functions are marked throws in the generated signature. Catch errors with do/catch.
do {
    try await context.generateKeypackage(credentialRef: credRef, lifetime: nil)
} catch let error as CoreCryptoError {
    // handle error
}
The thrown type is CoreCryptoError, which wraps MlsError and ProteusError variants.

Build docs developers (and LLMs) love