/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! 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; //! fn put(&self, key: String, value: String); //! } //! ``` //! //! 2. Define a callback interface in the UDL //! //! ```idl //! callback interface Keychain { //! string? get(string key); //! void put(string key, string data); //! }; //! ``` //! //! 3. 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: //! ```idl //! object Authenticator { //! constructor(Keychain keychain); //! void login(); //! } //! ``` //! //! In Rust: //! //! ``` //!# trait Keychain: Send { //!# fn get(&self, key: String) -> Option; //!# fn put(&self, key: String, value: String); //!# } //! struct Authenticator { //! keychain: Box, //! } //! //! impl Authenticator { //! pub fn new(keychain: Box) -> Self { //! Self { keychain } //! } //! pub fn login(&self) { //! let username = self.keychain.get("username".into()); //! let password = self.keychain.get("password".into()); //! } //! } //! ``` //! 4. Create an foreign language implementation of the callback interface. //! //! In this example, here's a Kotlin implementation. //! //! ```kotlin //! class AndroidKeychain: Keychain { //! override fun get(key: String): String? { //! // … elide the implementation. //! return value //! } //! override fun put(key: String) { //! // … elide the implementation. //! } //! } //! ``` //! 5. Pass the implementation to Rust. //! //! Again, in Kotlin //! //! ```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`. //! //! 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. //! use super::RustBuffer; use std::fmt; use std::os::raw::c_int; use std::sync::atomic::{AtomicUsize, Ordering}; /// 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. /// /// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object /// that implements the callback interface/trait. /// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from /// the IDL. The index is 1 indexed. Note that the list of methods is generated by at uniffi from the IDL and used in all /// bindings: so we can rely on the method list being stable within the same run of uniffi. /// * `args` is a serialized buffer of arguments to the function. UniFFI will deserialize it before /// passing individual arguments to the user's callback. /// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a /// buffer to write the result into. /// * A callback returns: /// - `-2` An error occurred that was serialized to buf_ptr /// - `-1` An unexpected error occurred /// - `0` is a deprecated way to signal that if the call succeeded, but did not modify buf_ptr /// - `1` If the call succeeded. For non-void functions the return value should be serialized /// to buf_ptr. /// Note: The output buffer might still contain 0 bytes of data. pub type ForeignCallback = unsafe extern "C" fn( handle: u64, method: u32, args: RustBuffer, buf_ptr: *mut RustBuffer, ) -> c_int; /// 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. pub const IDX_CALLBACK_FREE: u32 = 0; // Overly-paranoid sanity checking to ensure that these types are // convertible between each-other. `transmute` actually should check this for // us too, but this helps document the invariants we rely on in this code. // // Note that these are guaranteed by // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html // and thus this is a little paranoid. static_assertions::assert_eq_size!(usize, ForeignCallback); static_assertions::assert_eq_size!(usize, Option); /// Struct to hold a foreign callback. pub struct ForeignCallbackInternals { callback_ptr: AtomicUsize, } const EMPTY_PTR: usize = 0; impl ForeignCallbackInternals { pub const fn new() -> Self { ForeignCallbackInternals { callback_ptr: AtomicUsize::new(EMPTY_PTR), } } pub fn set_callback(&self, callback: ForeignCallback) { let as_usize = callback as usize; let old_ptr = self.callback_ptr.compare_exchange( EMPTY_PTR, as_usize, Ordering::SeqCst, Ordering::SeqCst, ); match old_ptr { // We get the previous value back. If this is anything except EMPTY_PTR, // then this has been set before we get here. Ok(EMPTY_PTR) => (), _ => // This is an internal bug, the other side of the FFI should ensure // it sets this only once. { panic!("Bug: call set_callback multiple times. This is likely a uniffi bug") } }; } pub fn get_callback(&self) -> Option { let ptr_value = self.callback_ptr.load(Ordering::SeqCst); unsafe { std::mem::transmute::>(ptr_value) } } } /// Used when internal/unexpected error happened when calling a foreign callback, for example when /// a unknown exception is raised /// /// User callback error types must implement a From impl from this type to their own error type. #[derive(Debug)] pub struct UnexpectedUniFFICallbackError { pub reason: String, } impl UnexpectedUniFFICallbackError { pub fn from_reason(reason: String) -> Self { Self { reason } } } impl fmt::Display for UnexpectedUniFFICallbackError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "UnexpectedUniFFICallbackError(reason: {:?})", self.reason ) } } impl std::error::Error for UnexpectedUniFFICallbackError {}