UniFFI ships with production-quality support for generating Swift bindings. Concepts from the UDL file map into Swift as follows:
- Primitive datatypes map to their obvious Swift counterpart, e.g.
- An object interface declared as
interface Tis represented as a Swift
protocol TProtocoland a concrete Swift
class Tthat conforms to it. Having the protocol declared explicitly can be useful for mocking instances of the class in unittests.
- A dictionary struct declared as
dictionary Tis represented as a Swift
struct Twith public mutable fields.
- An enum declared
[Enum] interface Tis represented as a Swift
enum Twith appropriate variants.
- Optional types are represented using Swift's builtin optional type syntax
- Sequences are represented as Swift arrays, and Maps as Swift dictionaries.
- Errors are represented as Swift enums that conform to the
- Function calls that have an associated error type are marked with
throws, and hence must be called using one of Swift's
- Failing assertions, Rust panics, and other unexpected errors in the generated code
are translated into a private enum conforming to the
- If this happens inside a throwing Swift function, it can be caught and handled
by a catch-all
catchstatement (but do so at your own risk, because it indicates that something has gone seriously wrong).
- If this happens inside a non-throwing Swift function, it will be converted into a fatal Swift error that cannot be caught.
- If this happens inside a throwing Swift function, it can be caught and handled by a catch-all
Conceptually, the generated bindings are split into two Swift modules, one for the low-level C FFI layer and one for the higher-level Swift bindings. For a UniFFI component named "example" we generate:
- A C header file
exampleFFI.hdeclaring the low-level structs and functions for calling into Rust, along with a corresponding
exampleFFI.modulemapto expose them to Swift.
- A Swift source file
example.swiftthat imports the
exampleFFImodule and wraps it to provide the higher-level Swift API.
Splitting up the bindings in this way gives you flexibility over how both the Rust code and the Swift code are distributed to consumers. For example, you may choose to compile and distribute the Rust code for several UniFFI components as a single shared library in order to reduce the compiled code size, while distributing their Swift wrappers as individual modules.
For more technical details on how the bindings work internally, please see the module documentation