Expand description

Callback interfaces are traits specified in UDL which can be implemented by foreign languages.

Using callback interfaces

  1. Define a Rust trait.

This toy example defines a way of Rust accessing a key-value store exposed by the host operating system (e.g. the key chain).

trait Keychain: Send {
  fn get(&self, key: String) -> Option<String>;
  fn put(&self, key: String, value: String);
}
  1. Define a callback interface in the UDL
callback interface Keychain {
    string? get(string key);
    void put(string key, string data);
};
  1. And allow it to be passed into Rust.

Here, we define a constructor to pass the keychain to rust, and then another method which may use it.

In UDL:

object Authenticator {
    constructor(Keychain keychain);
    void login();
}

In Rust:

struct Authenticator {
  keychain: Box<dyn Keychain>,
}

impl Authenticator {
  pub fn new(keychain: Box<dyn Keychain>) -> Self {
    Self { keychain }
  }
  pub fn login(&self) {
    let username = self.keychain.get("username".into());
    let password = self.keychain.get("password".into());
  }
}
  1. Create an foreign language implementation of the callback interface.

In this example, here’s a Kotlin implementation.

class AndroidKeychain: Keychain {
    override fun get(key: String): String? {
        // … elide the implementation.
        return value
    }
    override fun put(key: String) {
        // … elide the implementation.
    }
}
  1. Pass the implementation to Rust.

Again, in Kotlin

val authenticator = Authenticator(AndroidKeychain())
authenticator.login()

How it works.

High level

Uniffi generates a protocol or interface in client code in the foreign language must implement.

For each callback interface, UniFFI defines a VTable. This is a repr(C) struct where each field is a repr(C) callback function pointer. There is one field for each method, plus an extra field for the uniffi_free method. The foreign code registers one VTable per callback interface with Rust.

VTable methods have a similar signature to Rust scaffolding functions. The one difference is that values are returned via an out pointer to work around a Python bug (https://bugs.python.org/issue5710).

The foreign object that implements the interface is represented by an opaque handle. UniFFI generates a struct that implements the trait by calling VTable methods, passing the handle as the first parameter. When the struct is dropped, the uniffi_free method is called.

Structs

  • Used when internal/unexpected error happened when calling a foreign callback, for example when a unknown exception is raised