pub unsafe trait FfiConverter: Sized {
    type RustType;
    type FfiType;

    fn lower(obj: Self::RustType) -> Self::FfiType;
    fn try_lift(v: Self::FfiType) -> Result<Self::RustType>;
    fn write(obj: Self::RustType, buf: &mut Vec<u8>);
    fn try_read(buf: &mut &[u8]) -> Result<Self::RustType>;
}
Expand description

Trait defining how to transfer values via the FFI layer.

The FfiConverter trait defines how to pass values of a particular type back-and-forth over the uniffi generated FFI layer, both as standalone argument or return values, and as part of serialized compound data structures.

(This trait is like the IntoFfi trait from ffi_support, but local to this crate so that we can add some alternative implementations for different builtin types, and so that we can add support for receiving as well as returning).

Safety

This is an unsafe trait (implementing it requires unsafe impl) because we can’t guarantee that it’s safe to pass your type out to foreign-language code and back again. Buggy implementations of this trait might violate some assumptions made by the generated code, or might not match with the corresponding code in the generated foreign-language bindings.

In general, you should not need to implement this trait by hand, and should instead rely on implementations generated from your component UDL via the uniffi-bindgen scaffolding command.

Required Associated Types

The type used in Rust code.

For primitive / standard types, we implement FfiConverter on the type itself with RustType=Self. For user-defined types we create a unit struct and implement it there. This sidesteps Rust’s orphan rules (ADR-0006).

The low-level type used for passing values of this type over the FFI.

This must be a C-compatible type (e.g. a numeric primitive, a #[repr(C)] struct) into which values of the target rust type can be converted.

For complex data types, we currently recommend using RustBuffer and serializing the data for transfer. In theory it could be possible to build a matching #[repr(C)] struct for a complex data type and pass that instead, but explicit serialization is simpler and safer as a starting point.

Required Methods

Lower a rust value of the target type, into an FFI value of type Self::FfiType.

This trait method is used for sending data from rust to the foreign language code, by (hopefully cheaply!) converting it into someting that can be passed over the FFI and reconstructed on the other side.

Note that this method takes an owned Self::RustType; this allows it to transfer ownership in turn to the foreign language code, e.g. by boxing the value and passing a pointer.

Lift a rust value of the target type, from an FFI value of type Self::FfiType.

This trait method is used for receiving data from the foreign language code in rust, by (hopefully cheaply!) converting it from a low-level FFI value of type Self::FfiType into a high-level rust value of the target type.

Since we cannot statically guarantee that the foreign-language code will send valid values of type Self::FfiType, this method is fallible.

Write a rust value into a buffer, to send over the FFI in serialized form.

This trait method can be used for sending data from rust to the foreign language code, in cases where we’re not able to use a special-purpose FFI type and must fall back to sending serialized bytes.

Note that this method takes an owned Self::RustType because it’s transfering ownership to the foreign language code via the RustBuffer.

Read a rust value from a buffer, received over the FFI in serialized form.

This trait method can be used for receiving data from the foreign language code in rust, in cases where we’re not able to use a special-purpose FFI type and must fall back to receiving serialized bytes.

Since we cannot statically guarantee that the foreign-language code will send valid serialized bytes for the target type, this method is fallible.

Note the slightly unusual type here - we want a mutable reference to a slice of bytes, because we want to be able to advance the start of the slice after reading an item from it (but will not mutate the actual contents of the slice).

Implementations on Foreign Types

Support for passing boolean values via the FFI.

Booleans are passed as an i8 in order to avoid problems with handling C-compatible boolean values on JVM-based languages.

Support for passing Strings via the FFI.

Unlike many other implementations of FfiConverter, this passes a struct containing a raw pointer rather than copying the data from one side to the other. This is a safety hazard, but turns out to be pretty nice for useability. This struct must be a valid RustBuffer and it must contain valid utf-8 data (in other words, it must be a Vec<u8> suitable for use as an actual rust String).

When serialized in a buffer, strings are represented as a i32 byte length followed by utf8-encoded bytes. (It’s a signed integer because unsigned types are currently experimental in Kotlin).

Support for passing reference-counted shared objects via the FFI.

To avoid dealing with complex lifetime semantics over the FFI, any data passed by reference must be encapsulated in an Arc, and must be safe to share across threads.

When lowering, we have an owned Arc<T> and we transfer that ownership to the foreign-language code, “leaking” it out of Rust’s ownership system as a raw pointer. This works safely because we have unique ownership of self. The foreign-language code is responsible for freeing this by calling the ffi_object_free FFI function provided by the corresponding UniFFI type.

Safety: when freeing the resulting pointer, the foreign-language code must call the destructor function specific to the type T. Calling the destructor function for other types may lead to undefined behaviour.

When lifting, we receive a “borrow” of the Arc<T> that is owned by the foreign-language code, and make a clone of it for our own use.

Safety: the provided value must be a pointer previously obtained by calling the lower() or write() method of this impl.

When writing as a field of a complex structure, make a clone and transfer ownership of it to the foreign-language code by writing its pointer into the buffer. The foreign-language code is responsible for freeing this by calling the ffi_object_free FFI function provided by the corresponding UniFFI type.

Safety: when freeing the resulting pointer, the foreign-language code must call the destructor function specific to the type T. Calling the destructor function for other types may lead to undefined behaviour.

When reading as a field of a complex structure, we receive a “borrow” of the Arc<T> that is owned by the foreign-language code, and make a clone for our own use.

Safety: the buffer must contain a pointer previously obtained by calling the lower() or write() method of this impl.

Implementors