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, a CallbackInternals (on the Foreign Language side) and ForeignCallbackInternals (on Rust side) manages the process through a ForeignCallback. There is one ForeignCallback per callback interface.

Passing a callback interface implementation from foreign language (e.g. AndroidKeychain) into Rust causes the KeychainCallbackInternals to store the instance in a handlemap.

The object handle is passed over to Rust, and used to instantiate a struct KeychainProxy which implements the trait. This proxy implementation is generate by Uniffi. The KeychainProxy object is then passed to client code as Box<dyn Keychain>.

Methods on KeychainProxy objects (e.g. self.keychain.get("username".into())) encode the arguments into a RustBuffer. Using the ForeignCallback, it calls the CallbackInternals object on the foreign language side using the object handle, and the method selector.

The CallbackInternals object unpacks the arguments from the passed buffer, gets the object out from the handlemap, and calls the actual implementation of the method.

If there’s a return value, it is packed up in to another RustBuffer and used as the return value for ForeignCallback. The caller of ForeignCallback, the KeychainProxy unpacks the returned buffer into the correct type and then returns to client code.

Structs

Struct to hold a foreign callback.

Constants

The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, and it can be deleted from the handle map.

Type Definitions

ForeignCallback is the Rust representation of a foreign language function. It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, at library start up time. Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language.