UniFFI

repository·main·Indexed 26 days ago

https://github.com/mozilla/uniffi-rs

A multi-language bindings generator for Rust (version 0.32.0) that enables developers to write core logic once in Rust and expose it to platforms including Android (Kotlin), iOS (Swift), Python, and Ruby. It supports interface definitions via UDL files or proc-macros and provides tools for generating shared libraries and language-specific bindings.

Tokens
60.5K
Snippets
176
Records
367
Agent score
86%

What's inside uniffi

  1. Overview of UniFFI

    main

    UniFFI is a toolkit designed for building cross-platform software components in Rust. It allows you to write core business logic in Rust and describe its interface using an 'object model'. UniFFI then helps you:

    • Compile your Rust code into a shared library for various target platforms.
    • Generate bindings to load and use the library from different target languages.

    Supported languages include Kotlin, Swift, Python, and Ruby. Third-party bindings are also available for C# and Golang.

  2. Understand the Bindings IR Pipeline

    main

    The Bindings IR (Intermediate Representation) pipeline transforms different representations of generated bindings.

    Warning: The Bindings IR is currently an experiment. It is recommended that authors of other external bindings avoid using it for now as the system is not yet fully committed and is subject to change.

    Pipeline Components:

    • uniffi_pipeline crate: Defines foundational traits like Node and MapNode.
    • uniffi_internal_macros crate: Provides derive macros for Node and MapNode.
    • uniffi_bindgen crate: Defines the general pipeline (converts uniffi_meta metadata to initial IR, then to general IR).
    • Language-specific pipelines: Extend the general pipeline to output language-specific IR.
  3. Supported languages in UniFFI

    main

    UniFFI provides bindings for several languages. Unless otherwise noted, all features described in the manual are available for the primary supported languages:

    • Full Support: Kotlin, Swift, and Python.
    • Ruby Support: Most features work for Ruby, but it is considered a secondary target. Some features like docstrings in generated bindings are not available. Check the Ruby configuration for specific options.
    • 3rd Party Bindings: Various other languages are supported via third-party bindings. Note that these may require older versions of UniFFI or have partial feature support.
  4. Understand Rust FFI conversion traits in UniFFI

    main
    UniFFI uses a set of FFI converter traits to implement lifting (converting FFI values to Rust types) and lowering (converting Rust types to FFI values). Each trait handles a specific step in the process, such as lifting an argument or lowering a return value. This trait-based approach allows UniFFI to handle type aliases (like type MyTypeAlias = String) correctly, as the proc-macros reason about the traits rather than just the raw tokens.
  5. Understand object instance management via Arc pointers

    main

    UniFFI manages object instances by passing raw pointers to Rust's built-in Arc<T> across the FFI boundary, rather than using the older HandleMap abstraction.

    This approach allows for more flexible lifetimes, enabling object instances to be:

    • Passed as arguments to functions.
    • Returned from functions or methods (other than constructors).
    • Stored in collections like dictionaries in the foreign language.
    • Used as record fields.

    Important Considerations:

    • Reference Cycles: Because UniFFI uses reference counting (Arc), creating reference cycles between Rust objects may prevent them from being deallocated. Consumers from garbage-collected languages (like Kotlin, Python, or Swift) should be aware that these cycles will not be automatically collected by the host language's GC.
    • Safety: While the generated code is designed to be safe, the underlying mechanism relies on raw pointers. Misuse of the generated APIs could potentially lead to memory issues like 'use-after-free'.
  6. Use uniffi_parse_rs for metadata extraction

    main

    The uniffi_parse_rs crate is an experimental tool used to parse Rust source code into UniFFI metadata. This metadata is subsequently used to generate language bindings.

    Note: This crate is currently experimental and is not supported by any existing bindings.

  7. Understand the `uniffi-bindgen-kotlin-jni` architecture

    main

    uniffi-bindgen-kotlin-jni is an experimental bindgen system that generates JNI-based Rust scaffolding and Kotlin bindings.

    Key architectural details:

    • Code Generation: Uses uniffi_parse_rs to parse Rust source and uniffi_pipeline with Askama templates to generate scaffolding for a single crate.
    • JNI Implementation: Uses the low-level jni_sys crate instead of the high-level jni crate to achieve better performance (benchmarks suggest a 2-4x improvement) and reduce dependency weight.
    • Package Structure: Generates a dedicated uniffi package containing FFI functions to reduce namespace pollution in the consumer-facing package and simplify external type handling.
  8. Understand how UniFFI handles complex datatypes

    main
    UniFFI passes complex datatypes (such as Rust structs with named fields) across the Foreign Function Interface (FFI) using a simple, direct serialization scheme. Instead of passing raw memory layouts or using third-party libraries like Protocol Buffers, UniFFI serializes these types into a bytebuffer. This approach ensures memory safety by strictly controlling shared access to memory on both the Rust and the foreign-language sides of the boundary.
  9. Understand the UniFFI Bindings IR Pipeline

    main

    UniFFI uses a compiler-like pipeline to transform Rust definitions into multi-language bindings. The pipeline consists of five main stages:

    1. Metadata Generation: Created from Rust proc-macros (e.g., #[uniffi::export]) or UDL files. This is a simple reflection of Rust definitions.
    2. Initial IR: Converts the flat metadata into a tree structure.
    3. General IR: Adds FFI-specific information, such as scaffolding function names, cloning/freeing logic, and FFI type mappings.
    4. Language-specific IR: Specializes the IR for a target language (e.g., Python, Swift, Kotlin) by adding concrete type names, language-specific naming conventions (like camelCase), and FFI converter classes.
    5. Generated Bindings Code: Uses the language-specific IR and the Askama template engine to produce the final source code.

    Note for authors of new external bindings: The Bindings IR is currently unstable. It is recommended to use this pipeline over the older ComponentInterface for new bindings, though you should expect breaking changes as the pipeline stabilizes.

  10. Understand the purpose of UniFFI

    main

    UniFFI is a custom tool designed to automate the generation of foreign-language bindings for Rust code. It replaces the manual, error-prone process of writing C-compatible FFI layers and subsequent bindings for target languages like Swift and Kotlin.

    By using UniFFI, developers can:

    • Reduce the time required to launch new Rust components.
    • Improve the maintainability of existing components by reducing boilerplate.
    • Minimize human error in hand-written foreign language bindings.
    • Maintain a consistent approach to cross-compiling Rust code for multiple target platforms (e.g., Android, iOS, and Desktop).
  11. Understand the UniFFI MVP design approach

    main

    The initial Minimum Viable Product (MVP) of UniFFI was designed around three core principles to prioritize speed of development and flexibility for early consumers:

    1. API Specification via WebIDL: Instead of inferring APIs from Rust code, the MVP uses external WebIDL (Web Interface Definition Language) files to define the component interface. This requires developers to maintain the API definition in the IDL file separately from the Rust implementation.
    2. Manual Workflow: The tool is provided as a command-line utility (uniffi-bindgen) that developers install and run manually (or integrate into their own build processes) rather than being deeply integrated into the cargo build lifecycle via macros.
    3. Broad Capability over Depth: The tool aims to support a wide range of data types and API capabilities early on, even if the generated code is not yet highly optimized for performance.
  12. Understand UniFFI Async Implementation

    main
    UniFFI implements asynchronous functionality by piggybacking on the foreign language's runtime rather than forcing a specific Rust runtime (like tokio). The generated Rust code schedules work using callbacks provided by the foreign bindings. This allows library authors to integrate with the application's existing event loop and avoids the need for the Rust library to manage its own event loop threads.