summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_core
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_core')
-rw-r--r--third_party/rust/uniffi_core/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_core/Cargo.toml6
-rw-r--r--third_party/rust/uniffi_core/README.md81
-rw-r--r--third_party/rust/uniffi_core/src/ffi/callbackinterface.rs125
-rw-r--r--third_party/rust/uniffi_core/src/ffi/ffidefault.rs11
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs116
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs487
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreignfuture.rs241
-rw-r--r--third_party/rust/uniffi_core/src/ffi/handle.rs46
-rw-r--r--third_party/rust/uniffi_core/src/ffi/mod.rs6
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustbuffer.rs122
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustcalls.rs9
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture.rs735
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs320
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs141
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs96
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs223
-rw-r--r--third_party/rust/uniffi_core/src/ffi_converter_impls.rs75
-rw-r--r--third_party/rust/uniffi_core/src/ffi_converter_traits.rs151
-rw-r--r--third_party/rust/uniffi_core/src/lib.rs6
-rw-r--r--third_party/rust/uniffi_core/src/metadata.rs53
21 files changed, 1445 insertions, 1607 deletions
diff --git a/third_party/rust/uniffi_core/.cargo-checksum.json b/third_party/rust/uniffi_core/.cargo-checksum.json
index 59804f7c89..573f7a72c9 100644
--- a/third_party/rust/uniffi_core/.cargo-checksum.json
+++ b/third_party/rust/uniffi_core/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"b074f0db902264714faf879e99bdbc07df1550d75694f96751b499f98fecd16d","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"9e8650f0df087bf5e030a13d28f4990079e53613e656789b4b539d937a7fd288","src/ffi/ffidefault.rs":"f1ce099b92adbb12b160d513bae93342c7b6d806d7f6ebb665067db10af9a681","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"af8129a69ef23b92859e1cca0d666c95f0ed2c1fb2797f4495d824b65f774d03","src/ffi/foreignexecutor.rs":"123687921ce6dfb7f5bfa0736a630cfeff7f376b776ea03fc651da21ffd1cab8","src/ffi/mod.rs":"8117b08bbb7af3e97f66ed69c9690b60e8da0d6d8940349c7b9659a47cd8c92f","src/ffi/rustbuffer.rs":"8cc1f94b9ecba52b911da6a68155921c1b7f51b899d9874ddbc281a379941473","src/ffi/rustcalls.rs":"7caaa35ba8898c4b4983f07cefa80584ba00e753a11d496e578c80abe0cabe8b","src/ffi/rustfuture.rs":"d240426c8c8b83e3f6a2c0013e905298611287b2bb2022eb8161532209c635ca","src/ffi_converter_impls.rs":"82c1b47e02718610f2a5556997cd29ba5d8daf149d6353f470be0d9b971d968a","src/ffi_converter_traits.rs":"646c0d4aeb807d3e40db4d289f909030d0b2684087871a7d40d337680096b7d6","src/lib.rs":"4ad1a2899944a20e80a55d1c7bd01ff28395ace743a083c65847e6ea216fc5c8","src/metadata.rs":"6520ffcf2568a0d95f0f854acb6fc8aeaae26ef1f23fc576c2c50db72aa30eee","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"6121a127a3af1665cd90d12dd2b3683c2643c5103281d0fed5838324ca1fad5b"} \ No newline at end of file
+{"files":{"Cargo.toml":"c8969fbc6e8f6694e260ab78c94f9b4195d61afb7836b4c130b542d3b91b9200","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"f0184cf76bd86abb2815d260a87f85bd7060f5373ac6ef6f71955ece2a5075af","src/ffi/ffidefault.rs":"0db83fbcbc274c4c0daf7fb27833400568839b77a3496155840734c511d801e0","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"2b820a34b78705f5debc302a25c64d515a4aa7b3bdade083f4c1cfa2803664ae","src/ffi/foreignfuture.rs":"c1d621e41ea6af0c1d3959b46af8567c3fdc4164e7a82d635fcbb1da2c0737ac","src/ffi/handle.rs":"91f91469a81cb19edebb8bba433df62658cc66f6b54d5dc8520eb5793a85abd9","src/ffi/mod.rs":"30eea545299747838bf11b0698cfb71cedd3ca04d8cfb703c53198fcc44045c1","src/ffi/rustbuffer.rs":"0e725347f916834b17156413f406d5ca6c064b2cbc7437b051fe6692ad72c2aa","src/ffi/rustcalls.rs":"51c6499871c7d5eb4f80cabc806f26dd1df3b1090a2419d0d967aa9c5299a0a6","src/ffi/rustfuture/future.rs":"426cd0ad3c8cf008a7052a7d89856b6c6d5053b94e24325f5666d0281a40ec7f","src/ffi/rustfuture/mod.rs":"44568267e591f5b37f77acfdd6e60ae55ce48ab0a17fd81af3aeb31baa3d53e6","src/ffi/rustfuture/scheduler.rs":"c6484fff14c04596df5f306f2090366435dcff92561d317fde1ea9c097a9576b","src/ffi/rustfuture/tests.rs":"211241fb484a3a103eb0418e7d295850ea021bcd583fa1488f5efc68f33d5ab8","src/ffi_converter_impls.rs":"397c813f2e765462d7a7be524e6ac75e813a91a8ffd11c7e7df05f853213f77b","src/ffi_converter_traits.rs":"24c8cf6ada9b2f63b265e62c0f9092d640e533d0d7234e9156f92c3d1902f430","src/lib.rs":"1f6a031bbb160dfe46455a8bc24596f63b1e478f45579bfff62a62f58900bee4","src/metadata.rs":"83e463c377c0f501e58ac4eb5cc47c433c1473cecd47305fa89283e736b48d96","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"0ea3eb5474d50fc149b7e4d86b9c5bd4a61dcc167f0683902bf18ae7bbb3deef"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_core/Cargo.toml b/third_party/rust/uniffi_core/Cargo.toml
index 4d4cbe2758..ce36a2168a 100644
--- a/third_party/rust/uniffi_core/Cargo.toml
+++ b/third_party/rust/uniffi_core/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_core"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (runtime support code)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -44,7 +45,7 @@ version = "0.4"
version = "1.10.0"
[dependencies.oneshot]
-version = "0.1.5"
+version = "0.1.6"
features = ["async"]
package = "oneshot-uniffi"
@@ -56,5 +57,4 @@ version = "1.1.0"
[features]
default = []
-extern-rustbuffer = []
tokio = ["dep:async-compat"]
diff --git a/third_party/rust/uniffi_core/README.md b/third_party/rust/uniffi_core/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_core/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs
index 7be66880bb..e7a4faab64 100644
--- a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs
+++ b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs
@@ -91,121 +91,20 @@
//!
//! 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.
+//! 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.
//!
-//! 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.
+//! 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.
-use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer};
use std::fmt;
-/// 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;
-
-/// Result of a foreign callback invocation
-#[repr(i32)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum CallbackResult {
- /// Successful call.
- /// The return value is serialized to `buf_ptr`.
- Success = 0,
- /// Expected error.
- /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result.
- /// The error value is serialized to `buf_ptr`.
- Error = 1,
- /// Unexpected error.
- /// An error message string is serialized to `buf_ptr`.
- UnexpectedError = 2,
-}
-
-impl TryFrom<i32> for CallbackResult {
- // On errors we return the unconverted value
- type Error = i32;
-
- fn try_from(value: i32) -> Result<Self, i32> {
- match value {
- 0 => Ok(Self::Success),
- 1 => Ok(Self::Error),
- 2 => Ok(Self::UnexpectedError),
- n => Err(n),
- }
- }
-}
-
-/// Struct to hold a foreign callback.
-pub struct ForeignCallbackInternals {
- callback_cell: ForeignCallbackCell,
-}
-
-impl ForeignCallbackInternals {
- pub const fn new() -> Self {
- ForeignCallbackInternals {
- callback_cell: ForeignCallbackCell::new(),
- }
- }
-
- pub fn set_callback(&self, callback: ForeignCallback) {
- self.callback_cell.set(callback);
- }
-
- /// Invoke a callback interface method on the foreign side and return the result
- pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R
- where
- R: LiftReturn<UniFfiTag>,
- {
- let mut ret_rbuf = RustBuffer::new();
- let callback = self.callback_cell.get();
- let raw_result = unsafe {
- callback(
- handle,
- method,
- args.data_pointer(),
- args.len() as i32,
- &mut ret_rbuf,
- )
- };
- let result = CallbackResult::try_from(raw_result)
- .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}"));
- match result {
- CallbackResult::Success => R::lift_callback_return(ret_rbuf),
- CallbackResult::Error => R::lift_callback_error(ret_rbuf),
- CallbackResult::UnexpectedError => {
- let reason = if !ret_rbuf.is_empty() {
- match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) {
- Ok(s) => s,
- Err(e) => {
- log::error!("{{ trait_name }} Error reading ret_buf: {e}");
- String::from("[Error reading reason]")
- }
- }
- } else {
- RustBuffer::destroy(ret_rbuf);
- String::from("[Unknown Reason]")
- };
- R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason })
- }
- }
- }
-}
-
/// Used when internal/unexpected error happened when calling a foreign callback, for example when
/// a unknown exception is raised
///
@@ -216,8 +115,10 @@ pub struct UnexpectedUniFFICallbackError {
}
impl UnexpectedUniFFICallbackError {
- pub fn from_reason(reason: String) -> Self {
- Self { reason }
+ pub fn new(reason: impl fmt::Display) -> Self {
+ Self {
+ reason: reason.to_string(),
+ }
}
}
diff --git a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
index 1f86f6b13b..a992ab7384 100644
--- a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
+++ b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
@@ -39,6 +39,12 @@ impl FfiDefault for () {
fn ffi_default() {}
}
+impl FfiDefault for crate::Handle {
+ fn ffi_default() -> Self {
+ Self::default()
+ }
+}
+
impl FfiDefault for *const std::ffi::c_void {
fn ffi_default() -> Self {
std::ptr::null()
@@ -51,9 +57,10 @@ impl FfiDefault for crate::RustBuffer {
}
}
-impl FfiDefault for crate::ForeignExecutorHandle {
+impl FfiDefault for crate::ForeignFuture {
fn ffi_default() -> Self {
- Self(std::ptr::null())
+ extern "C" fn free(_handle: u64) {}
+ crate::ForeignFuture { handle: 0, free }
}
}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
index ac2463cd8e..326ff12747 100644
--- a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
+++ b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
@@ -8,96 +8,32 @@
//! code loads the exported library. For each callback type, we also define a "cell" type for
//! storing the callback.
-use std::sync::atomic::{AtomicUsize, Ordering};
-
-use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback};
-
-/// 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 list is 1 indexed. Note that the list of methods is generated by 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_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code
-/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the
-/// arguments from the buffer and passes them 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.
-/// * Callbacks return one of the `CallbackResult` values
-/// Note: The output buffer might still contain 0 bytes of data.
-pub type ForeignCallback = unsafe extern "C" fn(
- handle: u64,
- method: u32,
- args_data: *const u8,
- args_len: i32,
- buf_ptr: *mut RustBuffer,
-) -> i32;
-
-/// Callback to schedule a Rust call with a `ForeignExecutor`. The bindings code registers exactly
-/// one of these with the Rust code.
-///
-/// Delay is an approximate amount of ms to wait before scheduling the call. Delay is usually 0,
-/// which means schedule sometime soon.
-///
-/// As a special case, when Rust drops the foreign executor, with `task=null`. The foreign
-/// bindings should release the reference to the executor that was reserved for Rust.
-///
-/// This callback can be invoked from any thread, including threads created by Rust.
-///
-/// The callback should return one of the `ForeignExecutorCallbackResult` values.
-pub type ForeignExecutorCallback = extern "C" fn(
- executor: ForeignExecutorHandle,
- delay: u32,
- task: Option<RustTaskCallback>,
- task_data: *const (),
-) -> i8;
-
-/// Store a [ForeignCallback] pointer
-pub(crate) struct ForeignCallbackCell(AtomicUsize);
-
-/// Store a [ForeignExecutorCallback] pointer
-pub(crate) struct ForeignExecutorCallbackCell(AtomicUsize);
-
-/// Macro to define foreign callback types as well as the callback cell.
-macro_rules! impl_foreign_callback_cell {
- ($callback_type:ident, $cell_type:ident) => {
- // 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, $callback_type);
- static_assertions::assert_eq_size!(usize, Option<$callback_type>);
-
- impl $cell_type {
- pub const fn new() -> Self {
- Self(AtomicUsize::new(0))
- }
-
- pub fn set(&self, callback: $callback_type) {
- // Store the pointer using Ordering::Relaxed. This is sufficient since callback
- // should be set at startup, before there's any chance of using them.
- self.0.store(callback as usize, Ordering::Relaxed);
- }
-
- pub fn get(&self) -> $callback_type {
- let ptr_value = self.0.load(Ordering::Relaxed);
- unsafe {
- // SAFETY: self.0 was set in `set` from our function pointer type, so
- // it's safe to transmute it back here.
- ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value)
- .expect("Bug: callback not set. This is likely a uniffi bug.")
- }
- }
+use std::{
+ ptr::{null_mut, NonNull},
+ sync::atomic::{AtomicPtr, Ordering},
+};
+
+// Cell type that stores any NonNull<T>
+#[doc(hidden)]
+pub struct UniffiForeignPointerCell<T>(AtomicPtr<T>);
+
+impl<T> UniffiForeignPointerCell<T> {
+ pub const fn new() -> Self {
+ Self(AtomicPtr::new(null_mut()))
+ }
+
+ pub fn set(&self, callback: NonNull<T>) {
+ self.0.store(callback.as_ptr(), Ordering::Relaxed);
+ }
+
+ pub fn get(&self) -> &T {
+ unsafe {
+ NonNull::new(self.0.load(Ordering::Relaxed))
+ .expect("Foreign pointer not set. This is likely a uniffi bug.")
+ .as_mut()
}
- };
+ }
}
-impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell);
-impl_foreign_callback_cell!(ForeignExecutorCallback, ForeignExecutorCallbackCell);
+unsafe impl<T> Send for UniffiForeignPointerCell<T> {}
+unsafe impl<T> Sync for UniffiForeignPointerCell<T> {}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs
deleted file mode 100644
index 7b1cb9bd80..0000000000
--- a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs
+++ /dev/null
@@ -1,487 +0,0 @@
-/* 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/. */
-
-//! Schedule tasks using a foreign executor.
-
-use std::panic;
-
-use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell};
-
-/// Opaque handle for a foreign task executor.
-///
-/// Foreign code can either use an actual pointer, or use an integer value casted to it.
-#[repr(transparent)]
-#[derive(Clone, Copy, Debug)]
-pub struct ForeignExecutorHandle(pub(crate) *const ());
-
-// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for
-// making the `ForeignExecutorCallback` thread-safe.
-unsafe impl Send for ForeignExecutorHandle {}
-
-unsafe impl Sync for ForeignExecutorHandle {}
-
-/// Result code returned by `ForeignExecutorCallback`
-#[repr(i8)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum ForeignExecutorCallbackResult {
- /// Callback was scheduled successfully
- Success = 0,
- /// Callback couldn't be scheduled because the foreign executor is canceled/closed.
- Cancelled = 1,
- /// Callback couldn't be scheduled because of some other error
- Error = 2,
-}
-
-impl ForeignExecutorCallbackResult {
- /// Check the result code for the foreign executor callback
- ///
- /// If the result was `ForeignExecutorCallbackResult.Success`, this method returns `true`.
- ///
- /// If not, this method returns `false`, logging errors for any unexpected return values
- pub fn check_result_code(result: i8) -> bool {
- match result {
- n if n == ForeignExecutorCallbackResult::Success as i8 => true,
- n if n == ForeignExecutorCallbackResult::Cancelled as i8 => false,
- n if n == ForeignExecutorCallbackResult::Error as i8 => {
- log::error!(
- "ForeignExecutorCallbackResult::Error returned by foreign executor callback"
- );
- false
- }
- n => {
- log::error!("Unknown code ({n}) returned by foreign executor callback");
- false
- }
- }
- }
-}
-
-// Option<RustTaskCallback> should use the null pointer optimization and be represented in C as a
-// regular pointer. Let's check that.
-static_assertions::assert_eq_size!(usize, Option<RustTaskCallback>);
-
-/// Callback for a Rust task, this is what the foreign executor invokes
-///
-/// The task will be passed the `task_data` passed to `ForeignExecutorCallback` in addition to one
-/// of the `RustTaskCallbackCode` values.
-pub type RustTaskCallback = extern "C" fn(*const (), RustTaskCallbackCode);
-
-/// Passed to a `RustTaskCallback` function when the executor invokes them.
-///
-/// Every `RustTaskCallback` will be invoked eventually, this code is used to distinguish the times
-/// when it's invoked successfully vs times when the callback is being called because the foreign
-/// executor has been cancelled / shutdown
-#[repr(i8)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum RustTaskCallbackCode {
- /// Successful task callback invocation
- Success = 0,
- /// The `ForeignExecutor` has been cancelled.
- ///
- /// This signals that any progress using the executor should be halted. In particular, Futures
- /// should not continue to progress.
- Cancelled = 1,
-}
-
-static FOREIGN_EXECUTOR_CALLBACK: ForeignExecutorCallbackCell = ForeignExecutorCallbackCell::new();
-
-/// Set the global ForeignExecutorCallback. This is called by the foreign bindings, normally
-/// during initialization.
-pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) {
- FOREIGN_EXECUTOR_CALLBACK.set(callback);
-}
-
-/// Schedule Rust calls using a foreign executor
-#[derive(Debug)]
-pub struct ForeignExecutor {
- pub(crate) handle: ForeignExecutorHandle,
-}
-
-impl ForeignExecutor {
- pub fn new(executor: ForeignExecutorHandle) -> Self {
- Self { handle: executor }
- }
-
- /// Schedule a closure to be run.
- ///
- /// This method can be used for "fire-and-forget" style calls, where the calling code doesn't
- /// need to await the result.
- ///
- /// Closure requirements:
- /// - Send: since the closure will likely run on a different thread
- /// - 'static: since it runs at an arbitrary time, so all references need to be 'static
- /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data
- pub fn schedule<F: FnOnce() + Send + 'static + panic::UnwindSafe>(&self, delay: u32, task: F) {
- let leaked_ptr: *mut F = Box::leak(Box::new(task));
- if !schedule_raw(
- self.handle,
- delay,
- schedule_callback::<F>,
- leaked_ptr as *const (),
- ) {
- // If schedule_raw() failed, drop the leaked box since `schedule_callback()` has not been
- // scheduled to run.
- unsafe {
- drop(Box::<F>::from_raw(leaked_ptr));
- };
- }
- }
-
- /// Schedule a closure to be run and get a Future for the result
- ///
- /// Closure requirements:
- /// - Send: since the closure will likely run on a different thread
- /// - 'static: since it runs at an arbitrary time, so all references need to be 'static
- /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data
- pub async fn run<F, T>(&self, delay: u32, closure: F) -> T
- where
- F: FnOnce() -> T + Send + 'static + panic::UnwindSafe,
- T: Send + 'static,
- {
- // Create a oneshot channel to handle the future
- let (sender, receiver) = oneshot::channel();
- // We can use `AssertUnwindSafe` here because:
- // - The closure is unwind safe
- // - `Sender` is not marked unwind safe, maybe this is just an oversight in the oneshot
- // library. However, calling `send()` and dropping the Sender should certainly be
- // unwind safe. `send()` should probably not panic at all and if it does it shouldn't
- // do it in a way that breaks the Receiver.
- // - Calling `expect` may result in a panic, but this should should not break either the
- // Sender or Receiver.
- self.schedule(
- delay,
- panic::AssertUnwindSafe(move || {
- sender.send(closure()).expect("Error sending future result")
- }),
- );
- receiver.await.expect("Error receiving future result")
- }
-}
-
-/// Low-level schedule interface
-///
-/// When using this function, take care to ensure that the `ForeignExecutor` that holds the
-/// `ForeignExecutorHandle` has not been dropped.
-///
-/// Returns true if the callback was successfully scheduled
-pub(crate) fn schedule_raw(
- handle: ForeignExecutorHandle,
- delay: u32,
- callback: RustTaskCallback,
- data: *const (),
-) -> bool {
- let result_code = (FOREIGN_EXECUTOR_CALLBACK.get())(handle, delay, Some(callback), data);
- ForeignExecutorCallbackResult::check_result_code(result_code)
-}
-
-impl Drop for ForeignExecutor {
- fn drop(&mut self) {
- (FOREIGN_EXECUTOR_CALLBACK.get())(self.handle, 0, None, std::ptr::null());
- }
-}
-
-extern "C" fn schedule_callback<F>(data: *const (), status_code: RustTaskCallbackCode)
-where
- F: FnOnce() + Send + 'static + panic::UnwindSafe,
-{
- // No matter what, we need to call Box::from_raw() to balance the Box::leak() call.
- let task = unsafe { Box::from_raw(data as *mut F) };
- // Skip running the task for the `RustTaskCallbackCode::Cancelled` code
- if status_code == RustTaskCallbackCode::Success {
- run_task(task);
- }
-}
-
-/// Run a scheduled task, catching any panics.
-///
-/// If there are panics, then we will log a warning and return None.
-fn run_task<F: FnOnce() -> T + panic::UnwindSafe, T>(task: F) -> Option<T> {
- match panic::catch_unwind(task) {
- Ok(v) => Some(v),
- Err(cause) => {
- let message = if let Some(s) = cause.downcast_ref::<&'static str>() {
- (*s).to_string()
- } else if let Some(s) = cause.downcast_ref::<String>() {
- s.clone()
- } else {
- "Unknown panic!".to_string()
- };
- log::warn!("Error calling UniFFI callback function: {message}");
- None
- }
- }
-}
-
-#[cfg(test)]
-pub use test::MockEventLoop;
-
-#[cfg(test)]
-mod test {
- use super::*;
- use std::{
- future::Future,
- pin::Pin,
- sync::{
- atomic::{AtomicU32, Ordering},
- Arc, Mutex, Once,
- },
- task::{Context, Poll, Wake, Waker},
- };
-
- /// Simulate an event loop / task queue / coroutine scope on the foreign side
- ///
- /// This simply collects scheduled calls into a Vec for testing purposes.
- ///
- /// Most of the MockEventLoop methods are `pub` since it's also used by the `rustfuture` tests.
- pub struct MockEventLoop {
- // Wrap everything in a mutex since we typically share access to MockEventLoop via an Arc
- inner: Mutex<MockEventLoopInner>,
- }
-
- pub struct MockEventLoopInner {
- // calls that have been scheduled
- calls: Vec<(u32, Option<RustTaskCallback>, *const ())>,
- // has the event loop been shutdown?
- is_shutdown: bool,
- }
-
- unsafe impl Send for MockEventLoopInner {}
-
- static FOREIGN_EXECUTOR_CALLBACK_INIT: Once = Once::new();
-
- impl MockEventLoop {
- pub fn new() -> Arc<Self> {
- // Make sure we install a foreign executor callback that can deal with mock event loops
- FOREIGN_EXECUTOR_CALLBACK_INIT
- .call_once(|| foreign_executor_callback_set(mock_executor_callback));
-
- Arc::new(Self {
- inner: Mutex::new(MockEventLoopInner {
- calls: vec![],
- is_shutdown: false,
- }),
- })
- }
-
- /// Create a new ForeignExecutorHandle
- pub fn new_handle(self: &Arc<Self>) -> ForeignExecutorHandle {
- // To keep the memory management simple, we simply leak an arc reference for this. We
- // only create a handful of these in the tests so there's no need for proper cleanup.
- ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ())
- }
-
- pub fn new_executor(self: &Arc<Self>) -> ForeignExecutor {
- ForeignExecutor {
- handle: self.new_handle(),
- }
- }
-
- /// Get the current number of scheduled calls
- pub fn call_count(&self) -> usize {
- self.inner.lock().unwrap().calls.len()
- }
-
- /// Get the last scheduled call
- pub fn last_call(&self) -> (u32, Option<RustTaskCallback>, *const ()) {
- self.inner
- .lock()
- .unwrap()
- .calls
- .last()
- .cloned()
- .expect("no calls scheduled")
- }
-
- /// Run all currently scheduled calls
- pub fn run_all_calls(&self) {
- let mut inner = self.inner.lock().unwrap();
- let is_shutdown = inner.is_shutdown;
- for (_delay, callback, data) in inner.calls.drain(..) {
- if !is_shutdown {
- callback.unwrap()(data, RustTaskCallbackCode::Success);
- } else {
- callback.unwrap()(data, RustTaskCallbackCode::Cancelled);
- }
- }
- }
-
- /// Shutdown the eventloop, causing scheduled calls and future calls to be cancelled
- pub fn shutdown(&self) {
- self.inner.lock().unwrap().is_shutdown = true;
- }
- }
-
- // `ForeignExecutorCallback` that we install for testing
- extern "C" fn mock_executor_callback(
- handle: ForeignExecutorHandle,
- delay: u32,
- task: Option<RustTaskCallback>,
- task_data: *const (),
- ) -> i8 {
- let eventloop = handle.0 as *const MockEventLoop;
- let mut inner = unsafe { (*eventloop).inner.lock().unwrap() };
- if inner.is_shutdown {
- ForeignExecutorCallbackResult::Cancelled as i8
- } else {
- inner.calls.push((delay, task, task_data));
- ForeignExecutorCallbackResult::Success as i8
- }
- }
-
- #[test]
- fn test_schedule_raw() {
- extern "C" fn callback(data: *const (), _status_code: RustTaskCallbackCode) {
- unsafe {
- *(data as *mut u32) += 1;
- }
- }
-
- let eventloop = MockEventLoop::new();
-
- let value: u32 = 0;
- assert_eq!(eventloop.call_count(), 0);
-
- schedule_raw(
- eventloop.new_handle(),
- 0,
- callback,
- &value as *const u32 as *const (),
- );
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(value, 0);
-
- eventloop.run_all_calls();
- assert_eq!(eventloop.call_count(), 0);
- assert_eq!(value, 1);
- }
-
- #[test]
- fn test_schedule() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
- let value = Arc::new(AtomicU32::new(0));
- assert_eq!(eventloop.call_count(), 0);
-
- let value2 = value.clone();
- executor.schedule(0, move || {
- value2.fetch_add(1, Ordering::Relaxed);
- });
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(value.load(Ordering::Relaxed), 0);
-
- eventloop.run_all_calls();
- assert_eq!(eventloop.call_count(), 0);
- assert_eq!(value.load(Ordering::Relaxed), 1);
- }
-
- #[derive(Default)]
- struct MockWaker {
- wake_count: AtomicU32,
- }
-
- impl Wake for MockWaker {
- fn wake(self: Arc<Self>) {
- self.wake_count.fetch_add(1, Ordering::Relaxed);
- }
- }
-
- #[test]
- fn test_run() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
- let mock_waker = Arc::new(MockWaker::default());
- let waker = Waker::from(mock_waker.clone());
- let mut context = Context::from_waker(&waker);
- assert_eq!(eventloop.call_count(), 0);
-
- let mut future = executor.run(0, move || "test-return-value");
- unsafe {
- assert_eq!(
- Pin::new_unchecked(&mut future).poll(&mut context),
- Poll::Pending
- );
- }
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 0);
-
- eventloop.run_all_calls();
- assert_eq!(eventloop.call_count(), 0);
- assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 1);
- unsafe {
- assert_eq!(
- Pin::new_unchecked(&mut future).poll(&mut context),
- Poll::Ready("test-return-value")
- );
- }
- }
-
- #[test]
- fn test_drop() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
-
- drop(executor);
- // Calling drop should schedule a call with null task data.
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(eventloop.last_call().1, None);
- }
-
- // Test that cancelled calls never run
- #[test]
- fn test_cancelled_call() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
- // Create a shared counter
- let counter = Arc::new(AtomicU32::new(0));
- // schedule increments using both `schedule()` and run()`
- let counter_clone = Arc::clone(&counter);
- executor.schedule(0, move || {
- counter_clone.fetch_add(1, Ordering::Relaxed);
- });
- let counter_clone = Arc::clone(&counter);
- let future = executor.run(0, move || {
- counter_clone.fetch_add(1, Ordering::Relaxed);
- });
- // shutdown the eventloop before the scheduled call gets a chance to run.
- eventloop.shutdown();
- // `run_all_calls()` will cause the scheduled task callbacks to run, but will pass
- // `RustTaskCallbackCode::Cancelled` to it. This drop the scheduled closure without executing
- // it.
- eventloop.run_all_calls();
-
- assert_eq!(counter.load(Ordering::Relaxed), 0);
- drop(future);
- }
-
- // Test that when scheduled calls are cancelled, the closures are dropped properly
- #[test]
- fn test_cancellation_drops_closures() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
-
- // Create an Arc<> that we will move into the closures to test if they are dropped or not
- let arc = Arc::new(0);
- let arc_clone = Arc::clone(&arc);
- executor.schedule(0, move || assert_eq!(*arc_clone, 0));
- let arc_clone = Arc::clone(&arc);
- let future = executor.run(0, move || assert_eq!(*arc_clone, 0));
-
- // shutdown the eventloop and run the (cancelled) scheduled calls.
- eventloop.shutdown();
- eventloop.run_all_calls();
- // try to schedule some more calls now that the loop has been shutdown
- let arc_clone = Arc::clone(&arc);
- executor.schedule(0, move || assert_eq!(*arc_clone, 0));
- let arc_clone = Arc::clone(&arc);
- let future2 = executor.run(0, move || assert_eq!(*arc_clone, 0));
-
- // Drop the futures so they don't hold on to any references
- drop(future);
- drop(future2);
-
- // All of these closures should have been dropped by now, there only remaining arc
- // reference should be the original
- assert_eq!(Arc::strong_count(&arc), 1);
- }
-}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs
new file mode 100644
index 0000000000..be6a214e84
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs
@@ -0,0 +1,241 @@
+/* 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/. */
+
+//! This module defines a Rust Future that wraps an async foreign function call.
+//!
+//! The general idea is to create a [oneshot::Channel], hand the sender to the foreign side, and
+//! await the receiver side on the Rust side.
+//!
+//! The foreign side should:
+//! * Input a [ForeignFutureCallback] and a `u64` handle in their scaffolding function.
+//! This is the sender, converted to a raw pointer, and an extern "C" function that sends the result.
+//! * Return a [ForeignFuture], which represents the foreign task object corresponding to the async function.
+//! * Call the [ForeignFutureCallback] when the async function completes with:
+//! * The `u64` handle initially passed in
+//! * The `ForeignFutureResult` for the call
+//! * Wait for the [ForeignFutureHandle::free] function to be called to free the task object.
+//! If this is called before the task completes, then the task will be cancelled.
+
+use crate::{LiftReturn, RustCallStatus, UnexpectedUniFFICallbackError};
+
+/// Handle for a foreign future
+pub type ForeignFutureHandle = u64;
+
+/// Handle for a callback data associated with a foreign future.
+pub type ForeignFutureCallbackData = *mut ();
+
+/// Callback that's passed to a foreign async functions.
+///
+/// See `LiftReturn` trait for how this is implemented.
+pub type ForeignFutureCallback<FfiType> =
+ extern "C" fn(oneshot_handle: u64, ForeignFutureResult<FfiType>);
+
+/// C struct that represents the result of a foreign future
+#[repr(C)]
+pub struct ForeignFutureResult<T> {
+ // Note: for void returns, T is `()`, which isn't directly representable with C since it's a ZST.
+ // Foreign code should treat that case as if there was no `return_value` field.
+ return_value: T,
+ call_status: RustCallStatus,
+}
+
+/// Perform a call to a foreign async method
+
+/// C struct that represents the foreign future.
+///
+/// This is what's returned by the async scaffolding functions.
+#[repr(C)]
+pub struct ForeignFuture {
+ pub handle: ForeignFutureHandle,
+ pub free: extern "C" fn(handle: ForeignFutureHandle),
+}
+
+impl Drop for ForeignFuture {
+ fn drop(&mut self) {
+ (self.free)(self.handle)
+ }
+}
+
+unsafe impl Send for ForeignFuture {}
+
+pub async fn foreign_async_call<F, T, UT>(call_scaffolding_function: F) -> T
+where
+ F: FnOnce(ForeignFutureCallback<T::ReturnType>, u64) -> ForeignFuture,
+ T: LiftReturn<UT>,
+{
+ let (sender, receiver) = oneshot::channel::<ForeignFutureResult<T::ReturnType>>();
+ // Keep the ForeignFuture around, even though we don't ever use it.
+ // The important thing is that the ForeignFuture will be dropped when this Future is.
+ let _foreign_future =
+ call_scaffolding_function(foreign_future_complete::<T, UT>, sender.into_raw() as u64);
+ match receiver.await {
+ Ok(result) => T::lift_foreign_return(result.return_value, result.call_status),
+ Err(e) => {
+ // This shouldn't happen in practice, but we can do our best to recover
+ T::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(format!(
+ "Error awaiting foreign future: {e}"
+ )))
+ }
+ }
+}
+
+pub extern "C" fn foreign_future_complete<T: LiftReturn<UT>, UT>(
+ oneshot_handle: u64,
+ result: ForeignFutureResult<T::ReturnType>,
+) {
+ let channel = unsafe { oneshot::Sender::from_raw(oneshot_handle as *mut ()) };
+ // Ignore errors in send.
+ //
+ // Error means the receiver was already dropped which will happen when the future is cancelled.
+ let _ = channel.send(result);
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{Lower, RustBuffer};
+ use once_cell::sync::OnceCell;
+ use std::{
+ future::Future,
+ pin::Pin,
+ sync::{
+ atomic::{AtomicU32, Ordering},
+ Arc,
+ },
+ task::{Context, Poll, Wake},
+ };
+
+ struct MockForeignFuture {
+ freed: Arc<AtomicU32>,
+ callback_info: Arc<OnceCell<(ForeignFutureCallback<RustBuffer>, u64)>>,
+ rust_future: Option<Pin<Box<dyn Future<Output = String>>>>,
+ }
+
+ impl MockForeignFuture {
+ fn new() -> Self {
+ let callback_info = Arc::new(OnceCell::new());
+ let freed = Arc::new(AtomicU32::new(0));
+
+ let rust_future: Pin<Box<dyn Future<Output = String>>> = {
+ let callback_info = callback_info.clone();
+ let freed = freed.clone();
+ Box::pin(foreign_async_call::<_, String, crate::UniFfiTag>(
+ move |callback, data| {
+ callback_info.set((callback, data)).unwrap();
+ ForeignFuture {
+ handle: Arc::into_raw(freed) as *mut () as u64,
+ free: Self::free,
+ }
+ },
+ ))
+ };
+ let rust_future = Some(rust_future);
+ let mut mock_foreign_future = Self {
+ freed,
+ callback_info,
+ rust_future,
+ };
+ // Poll the future once, to start it up. This ensures that `callback_info` is set.
+ let _ = mock_foreign_future.poll();
+ mock_foreign_future
+ }
+
+ fn poll(&mut self) -> Poll<String> {
+ let waker = Arc::new(NoopWaker).into();
+ let mut context = Context::from_waker(&waker);
+ self.rust_future
+ .as_mut()
+ .unwrap()
+ .as_mut()
+ .poll(&mut context)
+ }
+
+ fn complete_success(&self, value: String) {
+ let (callback, data) = self.callback_info.get().unwrap();
+ callback(
+ *data,
+ ForeignFutureResult {
+ return_value: <String as Lower<crate::UniFfiTag>>::lower(value),
+ call_status: RustCallStatus::new(),
+ },
+ );
+ }
+
+ fn complete_error(&self, error_message: String) {
+ let (callback, data) = self.callback_info.get().unwrap();
+ callback(
+ *data,
+ ForeignFutureResult {
+ return_value: RustBuffer::default(),
+ call_status: RustCallStatus::error(error_message),
+ },
+ );
+ }
+
+ fn drop_future(&mut self) {
+ self.rust_future = None
+ }
+
+ fn free_count(&self) -> u32 {
+ self.freed.load(Ordering::Relaxed)
+ }
+
+ extern "C" fn free(handle: u64) {
+ let flag = unsafe { Arc::from_raw(handle as *mut AtomicU32) };
+ flag.fetch_add(1, Ordering::Relaxed);
+ }
+ }
+
+ struct NoopWaker;
+
+ impl Wake for NoopWaker {
+ fn wake(self: Arc<Self>) {}
+ }
+
+ #[test]
+ fn test_foreign_future() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ assert_eq!(mock_foreign_future.poll(), Poll::Pending);
+ mock_foreign_future.complete_success("It worked!".to_owned());
+ assert_eq!(
+ mock_foreign_future.poll(),
+ Poll::Ready("It worked!".to_owned())
+ );
+ // Since the future is complete, it should free the foreign future
+ assert_eq!(mock_foreign_future.free_count(), 1);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_foreign_future_error() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ assert_eq!(mock_foreign_future.poll(), Poll::Pending);
+ mock_foreign_future.complete_error("It Failed!".to_owned());
+ let _ = mock_foreign_future.poll();
+ }
+
+ #[test]
+ fn test_drop_after_complete() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ mock_foreign_future.complete_success("It worked!".to_owned());
+ assert_eq!(mock_foreign_future.free_count(), 0);
+ assert_eq!(
+ mock_foreign_future.poll(),
+ Poll::Ready("It worked!".to_owned())
+ );
+ // Dropping the future after it's complete should not panic, and not cause a double-free
+ mock_foreign_future.drop_future();
+ assert_eq!(mock_foreign_future.free_count(), 1);
+ }
+
+ #[test]
+ fn test_drop_before_complete() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ mock_foreign_future.complete_success("It worked!".to_owned());
+ // Dropping the future before it's complete should cancel the future
+ assert_eq!(mock_foreign_future.free_count(), 0);
+ mock_foreign_future.drop_future();
+ assert_eq!(mock_foreign_future.free_count(), 1);
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/handle.rs b/third_party/rust/uniffi_core/src/ffi/handle.rs
new file mode 100644
index 0000000000..8ee2f46c35
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/handle.rs
@@ -0,0 +1,46 @@
+/* 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/. */
+
+/// Object handle
+///
+/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in
+/// Rust and ones implemented in the foreign language.
+///
+/// Rust handles are generated by leaking a raw pointer
+/// Foreign handles are generated with a handle map that only generates odd values.
+/// For all currently supported architectures and hopefully any ones we add in the future:
+/// * 0 is an invalid value.
+/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the
+/// leaked pointer will be aligned).
+///
+/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait.
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
+#[repr(transparent)]
+pub struct Handle(u64);
+
+impl Handle {
+ pub fn from_pointer<T>(ptr: *const T) -> Self {
+ Self(ptr as u64)
+ }
+
+ pub fn as_pointer<T>(&self) -> *const T {
+ self.0 as *const T
+ }
+
+ pub fn from_raw(raw: u64) -> Option<Self> {
+ if raw == 0 {
+ None
+ } else {
+ Some(Self(raw))
+ }
+ }
+
+ pub fn from_raw_unchecked(raw: u64) -> Self {
+ Self(raw)
+ }
+
+ pub fn as_raw(&self) -> u64 {
+ self.0
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/mod.rs b/third_party/rust/uniffi_core/src/ffi/mod.rs
index b606323297..acaf2b0d06 100644
--- a/third_party/rust/uniffi_core/src/ffi/mod.rs
+++ b/third_party/rust/uniffi_core/src/ffi/mod.rs
@@ -8,7 +8,8 @@ pub mod callbackinterface;
pub mod ffidefault;
pub mod foreignbytes;
pub mod foreigncallbacks;
-pub mod foreignexecutor;
+pub mod foreignfuture;
+pub mod handle;
pub mod rustbuffer;
pub mod rustcalls;
pub mod rustfuture;
@@ -17,7 +18,8 @@ pub use callbackinterface::*;
pub use ffidefault::FfiDefault;
pub use foreignbytes::*;
pub use foreigncallbacks::*;
-pub use foreignexecutor::*;
+pub use foreignfuture::*;
+pub use handle::*;
pub use rustbuffer::*;
pub use rustcalls::*;
pub use rustfuture::*;
diff --git a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
index e09e3be89a..8b2972968c 100644
--- a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
+++ b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
@@ -52,11 +52,11 @@ use crate::ffi::{rust_call, ForeignBytes, RustCallStatus};
#[derive(Debug)]
pub struct RustBuffer {
/// The allocated capacity of the underlying `Vec<u8>`.
- /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
- capacity: i32,
+ /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple.
+ capacity: u64,
/// The occupied length of the underlying `Vec<u8>`.
- /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
- len: i32,
+ /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple.
+ len: u64,
/// The pointer to the allocated buffer of the `Vec<u8>`.
data: *mut u8,
}
@@ -84,7 +84,7 @@ impl RustBuffer {
/// # Safety
///
/// You must ensure that the raw parts uphold the documented invariants of this class.
- pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self {
+ pub unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self {
Self {
capacity,
len,
@@ -126,12 +126,8 @@ impl RustBuffer {
///
/// Panics if the requested size is too large to fit in an `i32`, and
/// hence would risk incompatibility with some foreign-language code.
- pub fn new_with_size(size: usize) -> Self {
- assert!(
- size < i32::MAX as usize,
- "RustBuffer requested size too large"
- );
- Self::from_vec(vec![0u8; size])
+ pub fn new_with_size(size: u64) -> Self {
+ Self::from_vec(vec![0u8; size as usize])
}
/// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`.
@@ -144,8 +140,8 @@ impl RustBuffer {
/// Panics if the vector's length or capacity are too large to fit in an `i32`,
/// and hence would risk incompatibility with some foreign-language code.
pub fn from_vec(v: Vec<u8>) -> Self {
- let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32.");
- let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32.");
+ let capacity = u64::try_from(v.capacity()).expect("buffer capacity cannot fit into a u64.");
+ let len = u64::try_from(v.len()).expect("buffer length cannot fit into a u64.");
let mut v = std::mem::ManuallyDrop::new(v);
unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) }
}
@@ -198,39 +194,18 @@ impl Default for RustBuffer {
}
}
-// extern "C" functions for the RustBuffer functionality.
+// Functions for the RustBuffer functionality.
//
-// These are used in two ways:
-// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer
-// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings.
-//
-// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL
-// hash This creates a separate set of functions for each UniFFIed component, which is needed
-// in the case where we create multiple dylib artifacts since each dylib will have its own
-// allocator.
+// The scaffolding code re-exports these functions, prefixed with the component name and UDL hash
+// This creates a separate set of functions for each UniFFIed component, which is needed in the
+// case where we create multiple dylib artifacts since each dylib will have its own allocator.
/// This helper allocates a new byte buffer owned by the Rust code, and returns it
/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually
/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined
/// below, or by passing ownership of the buffer back into Rust code.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_alloc(
- size: i32,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_alloc(size, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
-pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer {
- _uniffi_rustbuffer_alloc(size, call_status)
-}
-
-fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer {
- rust_call(call_status, || {
- Ok(RustBuffer::new_with_size(size.max(0) as usize))
- })
+pub fn uniffi_rustbuffer_alloc(size: u64, call_status: &mut RustCallStatus) -> RustBuffer {
+ rust_call(call_status, || Ok(RustBuffer::new_with_size(size)))
}
/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned
@@ -241,27 +216,10 @@ fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> Rust
/// # Safety
/// This function will dereference a provided pointer in order to copy bytes from it, so
/// make sure the `ForeignBytes` struct contains a valid pointer and length.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_from_bytes(
- bytes: ForeignBytes,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_from_bytes(bytes, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
pub fn uniffi_rustbuffer_from_bytes(
bytes: ForeignBytes,
call_status: &mut RustCallStatus,
) -> RustBuffer {
- _uniffi_rustbuffer_from_bytes(bytes, call_status)
-}
-
-fn _uniffi_rustbuffer_from_bytes(
- bytes: ForeignBytes,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
rust_call(call_status, || {
let bytes = bytes.as_slice();
Ok(RustBuffer::from_vec(bytes.to_vec()))
@@ -274,18 +232,7 @@ fn _uniffi_rustbuffer_from_bytes(
/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
/// corrupting the allocator state.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
- _uniffi_rustbuffer_free(buf, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
- _uniffi_rustbuffer_free(buf, call_status)
-}
-
-fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
rust_call(call_status, || {
RustBuffer::destroy(buf);
Ok(())
@@ -307,28 +254,9 @@ fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
/// corrupting the allocator state.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_reserve(
- buf: RustBuffer,
- additional: i32,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_reserve(buf, additional, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
pub fn uniffi_rustbuffer_reserve(
buf: RustBuffer,
- additional: i32,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_reserve(buf, additional, call_status)
-}
-
-fn _uniffi_rustbuffer_reserve(
- buf: RustBuffer,
- additional: i32,
+ additional: u64,
call_status: &mut RustCallStatus,
) -> RustBuffer {
rust_call(call_status, || {
@@ -394,24 +322,6 @@ mod test {
#[test]
#[should_panic]
- fn test_rustbuffer_provided_capacity_must_be_non_negative() {
- // We guard against foreign-language code providing this kind of invalid struct.
- let mut v = vec![0u8, 1, 2];
- let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) };
- rbuf.destroy_into_vec();
- }
-
- #[test]
- #[should_panic]
- fn test_rustbuffer_provided_len_must_be_non_negative() {
- // We guard against foreign-language code providing this kind of invalid struct.
- let mut v = vec![0u8, 1, 2];
- let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) };
- rbuf.destroy_into_vec();
- }
-
- #[test]
- #[should_panic]
fn test_rustbuffer_provided_len_must_not_exceed_capacity() {
// We guard against foreign-language code providing this kind of invalid struct.
let mut v = vec![0u8, 1, 2];
diff --git a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
index 53265393c0..16b0c76f2e 100644
--- a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
+++ b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
@@ -56,6 +56,13 @@ pub struct RustCallStatus {
}
impl RustCallStatus {
+ pub fn new() -> Self {
+ Self {
+ code: RustCallStatusCode::Success,
+ error_buf: MaybeUninit::new(RustBuffer::new()),
+ }
+ }
+
pub fn cancelled() -> Self {
Self {
code: RustCallStatusCode::Cancelled,
@@ -102,7 +109,7 @@ pub enum RustCallStatusCode {
/// Handle a scaffolding calls
///
/// `callback` is responsible for making the actual Rust call and returning a special result type:
-/// - For successfull calls, return `Ok(value)`
+/// - For successful calls, return `Ok(value)`
/// - For errors that should be translated into thrown exceptions in the foreign code, serialize
/// the error into a `RustBuffer`, then return `Ok(buf)`
/// - The success type, must implement `FfiDefault`.
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture.rs
deleted file mode 100644
index 0c1a24174b..0000000000
--- a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs
+++ /dev/null
@@ -1,735 +0,0 @@
-/* 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/. */
-
-//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI.
-//!
-//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`.
-//!
-//! # The big picture
-//!
-//! We implement async foreign functions using a simplified version of the Future API:
-//!
-//! 0. At startup, register a [RustFutureContinuationCallback] by calling
-//! rust_future_continuation_callback_set.
-//! 1. Call the scaffolding function to get a [RustFutureHandle]
-//! 2a. In a loop:
-//! - Call [rust_future_poll]
-//! - Suspend the function until the [rust_future_poll] continuation function is called
-//! - If the continuation was function was called with [RustFuturePoll::Ready], then break
-//! otherwise continue.
-//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the
-//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to
-//! enter a cancelled state.
-//! 3. Call [rust_future_complete] to get the result of the future.
-//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This:
-//! - Releases any resources held by the future
-//! - Calls any continuation callbacks that have not been called yet
-//!
-//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*`
-//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C",
-//! and manually monomorphized in the case of [rust_future_complete]. See
-//! `uniffi_macros/src/setup_scaffolding.rs` for details.
-//!
-//! ## How does `Future` work exactly?
-//!
-//! A [`Future`] in Rust does nothing. When calling an async function, it just
-//! returns a `Future` but nothing has happened yet. To start the computation,
-//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if
-//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically
-//! means:
-//!
-//! > Please, try to poll me later, maybe the result will be ready!
-//!
-//! This model is very different than what other languages do, but it can actually
-//! be translated quite easily, fortunately for us!
-//!
-//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does
-//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the
-//! `Future`: that's where they are polled.
-//!
-//! But… wait another minute… how does the executor know when to poll a [`Future`]?
-//! Does it poll them randomly in an endless loop? Well, no, actually it depends
-//! on the executor! A well-designed `Future` and executor work as follows.
-//! Normally, when [`Future::poll`] is called, a [`Context`] argument is
-//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a
-//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will
-//! signal the executor to poll a particular `Future`. A `Future` will clone
-//! or pass-by-ref the waker to somewhere, as a callback, a completion, a
-//! function, or anything, to the system that is responsible to notify when a
-//! task is completed. So, to recap, the waker is _not_ responsible for waking the
-//! `Future`, it _is_ responsible for _signaling_ the executor that a particular
-//! `Future` should be polled again. That's why the documentation of
-//! [`Poll::Pending`] specifies:
-//!
-//! > When a function returns `Pending`, the function must also ensure that the
-//! > current task is scheduled to be awoken when progress can be made.
-//!
-//! “awakening” is done by using the `Waker`.
-//!
-//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
-//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll
-//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready
-//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending
-//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html
-//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html
-//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html
-
-use std::{
- future::Future,
- marker::PhantomData,
- mem,
- ops::Deref,
- panic,
- pin::Pin,
- sync::{Arc, Mutex},
- task::{Context, Poll, Wake},
-};
-
-use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus};
-
-/// Result code for [rust_future_poll]. This is passed to the continuation function.
-#[repr(i8)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum RustFuturePoll {
- /// The future is ready and is waiting for [rust_future_complete] to be called
- Ready = 0,
- /// The future might be ready and [rust_future_poll] should be called again
- MaybeReady = 1,
-}
-
-/// Foreign callback that's passed to [rust_future_poll]
-///
-/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again
-/// to continue progress on the future.
-pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll);
-
-/// Opaque handle for a Rust future that's stored by the foreign language code
-#[repr(transparent)]
-pub struct RustFutureHandle(*const ());
-
-// === Public FFI API ===
-
-/// Create a new [RustFutureHandle]
-///
-/// For each exported async function, UniFFI will create a scaffolding function that uses this to
-/// create the [RustFutureHandle] to pass to the foreign code.
-pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle
-where
- // F is the future type returned by the exported async function. It needs to be Send + `static
- // since it will move between threads for an indeterminate amount of time as the foreign
- // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`,
- // since we synchronize all access to the values.
- F: Future<Output = T> + Send + 'static,
- // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send +
- // 'static for the same reason as F.
- T: LowerReturn<UT> + Send + 'static,
- // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy.
- UT: Send + 'static,
-{
- // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to
- // implement the FFI
- let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>;
- // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it
- // to the foreign code.
- let boxed_ffi = Box::new(future_ffi);
- // We can now create a RustFutureHandle
- RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ())
-}
-
-/// Poll a Rust future
-///
-/// When the future is ready to progress the continuation will be called with the `data` value and
-/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called
-/// exactly once.
-///
-/// # Safety
-///
-/// The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-pub unsafe fn rust_future_poll<ReturnType>(
- handle: RustFutureHandle,
- callback: RustFutureContinuationCallback,
- data: *const (),
-) {
- let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.clone().ffi_poll(callback, data)
-}
-
-/// Cancel a Rust future
-///
-/// Any current and future continuations will be immediately called with RustFuturePoll::Ready.
-///
-/// This is needed for languages like Swift, which continuation to wait for the continuation to be
-/// called when tasks are cancelled.
-///
-/// # Safety
-///
-/// The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) {
- let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.clone().ffi_cancel()
-}
-
-/// Complete a Rust future
-///
-/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for
-/// each supported FFI type.
-///
-/// # Safety
-///
-/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-/// - The `T` param must correctly correspond to the [rust_future_new] call. It must
-/// be `<Output as LowerReturn<UT>>::ReturnType`
-pub unsafe fn rust_future_complete<ReturnType>(
- handle: RustFutureHandle,
- out_status: &mut RustCallStatus,
-) -> ReturnType {
- let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.ffi_complete(out_status)
-}
-
-/// Free a Rust future, dropping the strong reference and releasing all references held by the
-/// future.
-///
-/// # Safety
-///
-/// The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) {
- let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.ffi_free()
-}
-
-/// Thread-safe storage for [RustFutureContinuationCallback] data
-///
-/// The basic guarantee is that all data pointers passed in are passed out exactly once to the
-/// foreign continuation callback. This enables us to uphold the [rust_future_poll] guarantee.
-///
-/// [ContinuationDataCell] also tracks cancellation, which is closely tied to continuation data.
-#[derive(Debug)]
-enum ContinuationDataCell {
- /// No continuations set, neither wake() nor cancel() called.
- Empty,
- /// `wake()` was called when there was no continuation set. The next time `store` is called,
- /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady`
- Waked,
- /// The future has been cancelled, any future `store` calls should immediately result in the
- /// continuation being called with `RustFuturePoll::Ready`.
- Cancelled,
- /// Continuation set, the next time `wake()` is called is called, we should invoke it.
- Set(RustFutureContinuationCallback, *const ()),
-}
-
-impl ContinuationDataCell {
- fn new() -> Self {
- Self::Empty
- }
-
- /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or
- /// `Cancelled` state, call the continuation immediately with the data.
- fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) {
- match self {
- Self::Empty => *self = Self::Set(callback, data),
- Self::Set(old_callback, old_data) => {
- log::error!(
- "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?"
- );
- old_callback(*old_data, RustFuturePoll::Ready);
- *self = Self::Set(callback, data);
- }
- Self::Waked => {
- *self = Self::Empty;
- callback(data, RustFuturePoll::MaybeReady);
- }
- Self::Cancelled => {
- callback(data, RustFuturePoll::Ready);
- }
- }
- }
-
- fn wake(&mut self) {
- match self {
- // If we had a continuation set, then call it and transition to the `Empty` state.
- Self::Set(callback, old_data) => {
- let old_data = *old_data;
- let callback = *callback;
- *self = Self::Empty;
- callback(old_data, RustFuturePoll::MaybeReady);
- }
- // If we were in the `Empty` state, then transition to `Waked`. The next time `store`
- // is called, we will immediately call the continuation.
- Self::Empty => *self = Self::Waked,
- // This is a no-op if we were in the `Cancelled` or `Waked` state.
- _ => (),
- }
- }
-
- fn cancel(&mut self) {
- if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) {
- callback(old_data, RustFuturePoll::Ready);
- }
- }
-
- fn is_cancelled(&self) -> bool {
- matches!(self, Self::Cancelled)
- }
-}
-
-// ContinuationDataCell is Send + Sync as long we handle the *const () pointer correctly
-
-unsafe impl Send for ContinuationDataCell {}
-unsafe impl Sync for ContinuationDataCell {}
-
-/// Wraps the actual future we're polling
-struct WrappedFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- // Note: this could be a single enum, but that would make it easy to mess up the future pinning
- // guarantee. For example you might want to call `std::mem::take()` to try to get the result,
- // but if the future happened to be stored that would move and break all internal references.
- future: Option<F>,
- result: Option<Result<T::ReturnType, RustCallStatus>>,
-}
-
-impl<F, T, UT> WrappedFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn new(future: F) -> Self {
- Self {
- future: Some(future),
- result: None,
- }
- }
-
- // Poll the future and check if it's ready or not
- fn poll(&mut self, context: &mut Context<'_>) -> bool {
- if self.result.is_some() {
- true
- } else if let Some(future) = &mut self.future {
- // SAFETY: We can call Pin::new_unchecked because:
- // - This is the only time we get a &mut to `self.future`
- // - We never poll the future after it's moved (for example by using take())
- // - We never move RustFuture, which contains us.
- // - RustFuture is private to this module so no other code can move it.
- let pinned = unsafe { Pin::new_unchecked(future) };
- // Run the poll and lift the result if it's ready
- let mut out_status = RustCallStatus::default();
- let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status(
- &mut out_status,
- // This closure uses a `&mut F` value, which means it's not UnwindSafe by
- // default. If the future panics, it may be in an invalid state.
- //
- // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None`
- // case below and we will never poll the future again.
- panic::AssertUnwindSafe(|| match pinned.poll(context) {
- Poll::Pending => Ok(Poll::Pending),
- Poll::Ready(v) => T::lower_return(v).map(Poll::Ready),
- }),
- );
- match result {
- Some(Poll::Pending) => false,
- Some(Poll::Ready(v)) => {
- self.future = None;
- self.result = Some(Ok(v));
- true
- }
- None => {
- self.future = None;
- self.result = Some(Err(out_status));
- true
- }
- }
- } else {
- log::error!("poll with neither future nor result set");
- true
- }
- }
-
- fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType {
- let mut return_value = T::ReturnType::ffi_default();
- match self.result.take() {
- Some(Ok(v)) => return_value = v,
- Some(Err(call_status)) => *out_status = call_status,
- None => *out_status = RustCallStatus::cancelled(),
- }
- self.free();
- return_value
- }
-
- fn free(&mut self) {
- self.future = None;
- self.result = None;
- }
-}
-
-// If F and T are Send, then WrappedFuture is too
-//
-// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising
-// that we will treat the raw pointer properly, for example by not returning it twice.
-unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
-}
-
-/// Future that the foreign code is awaiting
-struct RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- // This Mutex should never block if our code is working correctly, since there should not be
- // multiple threads calling [Self::poll] and/or [Self::complete] at the same time.
- future: Mutex<WrappedFuture<F, T, UT>>,
- continuation_data: Mutex<ContinuationDataCell>,
- // UT is used as the generic parameter for [LowerReturn].
- // Let's model this with PhantomData as a function that inputs a UT value.
- _phantom: PhantomData<fn(UT) -> ()>,
-}
-
-impl<F, T, UT> RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn new(future: F, _tag: UT) -> Arc<Self> {
- Arc::new(Self {
- future: Mutex::new(WrappedFuture::new(future)),
- continuation_data: Mutex::new(ContinuationDataCell::new()),
- _phantom: PhantomData,
- })
- }
-
- fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) {
- let ready = self.is_cancelled() || {
- let mut locked = self.future.lock().unwrap();
- let waker: std::task::Waker = Arc::clone(&self).into();
- locked.poll(&mut Context::from_waker(&waker))
- };
- if ready {
- callback(data, RustFuturePoll::Ready)
- } else {
- self.continuation_data.lock().unwrap().store(callback, data);
- }
- }
-
- fn is_cancelled(&self) -> bool {
- self.continuation_data.lock().unwrap().is_cancelled()
- }
-
- fn wake(&self) {
- self.continuation_data.lock().unwrap().wake();
- }
-
- fn cancel(&self) {
- self.continuation_data.lock().unwrap().cancel();
- }
-
- fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
- self.future.lock().unwrap().complete(call_status)
- }
-
- fn free(self: Arc<Self>) {
- // Call cancel() to send any leftover data to the continuation callback
- self.continuation_data.lock().unwrap().cancel();
- // Ensure we drop our inner future, releasing all held references
- self.future.lock().unwrap().free();
- }
-}
-
-impl<F, T, UT> Wake for RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn wake(self: Arc<Self>) {
- self.deref().wake()
- }
-
- fn wake_by_ref(self: &Arc<Self>) {
- self.deref().wake()
- }
-}
-
-/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to
-/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this
-/// allows you to use RustFuture functionality without knowing the concrete Future type, which is
-/// unnamable.
-///
-/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of
-/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need
-/// to create a poll, cancel, complete, and free scaffolding function for each exported async
-/// function. That would add ~1kb binary size per exported function based on a quick estimate on a
-/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and
-/// only create those functions for each of the 13 possible FFI return types.
-#[doc(hidden)]
-trait RustFutureFfi<ReturnType> {
- fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ());
- fn ffi_cancel(&self);
- fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType;
- fn ffi_free(self: Arc<Self>);
-}
-
-impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) {
- self.poll(callback, data)
- }
-
- fn ffi_cancel(&self) {
- self.cancel()
- }
-
- fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
- self.complete(call_status)
- }
-
- fn ffi_free(self: Arc<Self>) {
- self.free();
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode};
- use once_cell::sync::OnceCell;
- use std::task::Waker;
-
- // Sender/Receiver pair that we use for testing
- struct Channel {
- result: Option<Result<String, TestError>>,
- waker: Option<Waker>,
- }
-
- struct Sender(Arc<Mutex<Channel>>);
-
- impl Sender {
- fn wake(&self) {
- let inner = self.0.lock().unwrap();
- if let Some(waker) = &inner.waker {
- waker.wake_by_ref();
- }
- }
-
- fn send(&self, value: Result<String, TestError>) {
- let mut inner = self.0.lock().unwrap();
- if inner.result.replace(value).is_some() {
- panic!("value already sent");
- }
- if let Some(waker) = &inner.waker {
- waker.wake_by_ref();
- }
- }
- }
-
- struct Receiver(Arc<Mutex<Channel>>);
-
- impl Future for Receiver {
- type Output = Result<String, TestError>;
-
- fn poll(
- self: Pin<&mut Self>,
- context: &mut Context<'_>,
- ) -> Poll<Result<String, TestError>> {
- let mut inner = self.0.lock().unwrap();
- match &inner.result {
- Some(v) => Poll::Ready(v.clone()),
- None => {
- inner.waker = Some(context.waker().clone());
- Poll::Pending
- }
- }
- }
- }
-
- // Create a sender and rust future that we can use for testing
- fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) {
- let channel = Arc::new(Mutex::new(Channel {
- result: None,
- waker: None,
- }));
- let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag);
- (Sender(channel), rust_future)
- }
-
- /// Poll a Rust future and get an OnceCell that's set when the continuation is called
- fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> {
- let cell = Arc::new(OnceCell::new());
- let cell_ptr = Arc::into_raw(cell.clone()) as *const ();
- rust_future.clone().ffi_poll(poll_continuation, cell_ptr);
- cell
- }
-
- extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) {
- let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) };
- cell.set(code).expect("Error setting OnceCell");
- }
-
- fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) {
- let mut out_status_code = RustCallStatus::default();
- let return_value = rust_future.ffi_complete(&mut out_status_code);
- (return_value, out_status_code)
- }
-
- #[test]
- fn test_success() {
- let (sender, rust_future) = channel();
-
- // Test polling the rust future before it's ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- sender.wake();
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
-
- // Test polling the rust future when it's ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- sender.send(Ok("All done".into()));
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
-
- // Future polls should immediately return ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- // Complete the future
- let (return_buf, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Success);
- assert_eq!(
- <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
- "All done"
- );
- }
-
- #[test]
- fn test_error() {
- let (sender, rust_future) = channel();
-
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- sender.send(Err("Something went wrong".into()));
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
-
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- let (_, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Error);
- unsafe {
- assert_eq!(
- <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer(
- call_status.error_buf.assume_init()
- )
- .unwrap(),
- TestError::from("Something went wrong"),
- )
- }
- }
-
- // Once `complete` is called, the inner future should be released, even if wakers still hold a
- // reference to the RustFuture
- #[test]
- fn test_cancel() {
- let (_sender, rust_future) = channel();
-
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- rust_future.ffi_cancel();
- // Cancellation should immediately invoke the callback with RustFuturePoll::Ready
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- // Future polls should immediately invoke the callback with RustFuturePoll::Ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- let (_, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Cancelled);
- }
-
- // Once `free` is called, the inner future should be released, even if wakers still hold a
- // reference to the RustFuture
- #[test]
- fn test_release_future() {
- let (sender, rust_future) = channel();
- // Create a weak reference to the channel to use to check if rust_future has dropped its
- // future.
- let channel_weak = Arc::downgrade(&sender.0);
- drop(sender);
- // Create an extra ref to rust_future, simulating a waker that still holds a reference to
- // it
- let rust_future2 = rust_future.clone();
-
- // Complete the rust future
- rust_future.ffi_free();
- // Even though rust_future is still alive, the channel shouldn't be
- assert!(Arc::strong_count(&rust_future2) > 0);
- assert_eq!(channel_weak.strong_count(), 0);
- assert!(channel_weak.upgrade().is_none());
- }
-
- // If `free` is called with a continuation still stored, we should call it them then.
- //
- // This shouldn't happen in practice, but it seems like good defensive programming
- #[test]
- fn test_complete_with_stored_continuation() {
- let (_sender, rust_future) = channel();
-
- let continuation_result = poll(&rust_future);
- rust_future.ffi_free();
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
- }
-
- // Test what happens if we see a `wake()` call while we're polling the future. This can
- // happen, for example, with futures that are handled by a tokio thread pool. We should
- // schedule another poll of the future in this case.
- #[test]
- fn test_wake_during_poll() {
- let mut first_time = true;
- let future = std::future::poll_fn(move |ctx| {
- if first_time {
- first_time = false;
- // Wake the future while we are in the middle of polling it
- ctx.waker().clone().wake();
- Poll::Pending
- } else {
- // The second time we're polled, we're ready
- Poll::Ready("All done".to_owned())
- }
- });
- let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> =
- RustFuture::new(future, crate::UniFfiTag);
- let continuation_result = poll(&rust_future);
- // The continuation function should called immediately
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
- // A second poll should finish the future
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
- let (return_buf, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Success);
- assert_eq!(
- <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
- "All done"
- );
- }
-}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs
new file mode 100644
index 0000000000..93c34e7543
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs
@@ -0,0 +1,320 @@
+/* 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/. */
+
+//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI.
+//!
+//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`.
+//!
+//! # The big picture
+//!
+//! We implement async foreign functions using a simplified version of the Future API:
+//!
+//! 0. At startup, register a [RustFutureContinuationCallback] by calling
+//! rust_future_continuation_callback_set.
+//! 1. Call the scaffolding function to get a [Handle]
+//! 2a. In a loop:
+//! - Call [rust_future_poll]
+//! - Suspend the function until the [rust_future_poll] continuation function is called
+//! - If the continuation was function was called with [RustFuturePoll::Ready], then break
+//! otherwise continue.
+//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the
+//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to
+//! enter a cancelled state.
+//! 3. Call [rust_future_complete] to get the result of the future.
+//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This:
+//! - Releases any resources held by the future
+//! - Calls any continuation callbacks that have not been called yet
+//!
+//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*`
+//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C",
+//! and manually monomorphized in the case of [rust_future_complete]. See
+//! `uniffi_macros/src/setup_scaffolding.rs` for details.
+//!
+//! ## How does `Future` work exactly?
+//!
+//! A [`Future`] in Rust does nothing. When calling an async function, it just
+//! returns a `Future` but nothing has happened yet. To start the computation,
+//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if
+//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically
+//! means:
+//!
+//! > Please, try to poll me later, maybe the result will be ready!
+//!
+//! This model is very different than what other languages do, but it can actually
+//! be translated quite easily, fortunately for us!
+//!
+//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does
+//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the
+//! `Future`: that's where they are polled.
+//!
+//! But… wait another minute… how does the executor know when to poll a [`Future`]?
+//! Does it poll them randomly in an endless loop? Well, no, actually it depends
+//! on the executor! A well-designed `Future` and executor work as follows.
+//! Normally, when [`Future::poll`] is called, a [`Context`] argument is
+//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a
+//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will
+//! signal the executor to poll a particular `Future`. A `Future` will clone
+//! or pass-by-ref the waker to somewhere, as a callback, a completion, a
+//! function, or anything, to the system that is responsible to notify when a
+//! task is completed. So, to recap, the waker is _not_ responsible for waking the
+//! `Future`, it _is_ responsible for _signaling_ the executor that a particular
+//! `Future` should be polled again. That's why the documentation of
+//! [`Poll::Pending`] specifies:
+//!
+//! > When a function returns `Pending`, the function must also ensure that the
+//! > current task is scheduled to be awoken when progress can be made.
+//!
+//! “awakening” is done by using the `Waker`.
+//!
+//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
+//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll
+//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready
+//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending
+//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html
+//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html
+//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html
+
+use std::{
+ future::Future,
+ marker::PhantomData,
+ ops::Deref,
+ panic,
+ pin::Pin,
+ sync::{Arc, Mutex},
+ task::{Context, Poll, Wake},
+};
+
+use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler};
+use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus};
+
+/// Wraps the actual future we're polling
+struct WrappedFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ // Note: this could be a single enum, but that would make it easy to mess up the future pinning
+ // guarantee. For example you might want to call `std::mem::take()` to try to get the result,
+ // but if the future happened to be stored that would move and break all internal references.
+ future: Option<F>,
+ result: Option<Result<T::ReturnType, RustCallStatus>>,
+}
+
+impl<F, T, UT> WrappedFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ fn new(future: F) -> Self {
+ Self {
+ future: Some(future),
+ result: None,
+ }
+ }
+
+ // Poll the future and check if it's ready or not
+ fn poll(&mut self, context: &mut Context<'_>) -> bool {
+ if self.result.is_some() {
+ true
+ } else if let Some(future) = &mut self.future {
+ // SAFETY: We can call Pin::new_unchecked because:
+ // - This is the only time we get a &mut to `self.future`
+ // - We never poll the future after it's moved (for example by using take())
+ // - We never move RustFuture, which contains us.
+ // - RustFuture is private to this module so no other code can move it.
+ let pinned = unsafe { Pin::new_unchecked(future) };
+ // Run the poll and lift the result if it's ready
+ let mut out_status = RustCallStatus::default();
+ let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status(
+ &mut out_status,
+ // This closure uses a `&mut F` value, which means it's not UnwindSafe by
+ // default. If the future panics, it may be in an invalid state.
+ //
+ // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None`
+ // case below and we will never poll the future again.
+ panic::AssertUnwindSafe(|| match pinned.poll(context) {
+ Poll::Pending => Ok(Poll::Pending),
+ Poll::Ready(v) => T::lower_return(v).map(Poll::Ready),
+ }),
+ );
+ match result {
+ Some(Poll::Pending) => false,
+ Some(Poll::Ready(v)) => {
+ self.future = None;
+ self.result = Some(Ok(v));
+ true
+ }
+ None => {
+ self.future = None;
+ self.result = Some(Err(out_status));
+ true
+ }
+ }
+ } else {
+ log::error!("poll with neither future nor result set");
+ true
+ }
+ }
+
+ fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType {
+ let mut return_value = T::ReturnType::ffi_default();
+ match self.result.take() {
+ Some(Ok(v)) => return_value = v,
+ Some(Err(call_status)) => *out_status = call_status,
+ None => *out_status = RustCallStatus::cancelled(),
+ }
+ self.free();
+ return_value
+ }
+
+ fn free(&mut self) {
+ self.future = None;
+ self.result = None;
+ }
+}
+
+// If F and T are Send, then WrappedFuture is too
+//
+// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising
+// that we will treat the raw pointer properly, for example by not returning it twice.
+unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+}
+
+/// Future that the foreign code is awaiting
+pub(super) struct RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ // This Mutex should never block if our code is working correctly, since there should not be
+ // multiple threads calling [Self::poll] and/or [Self::complete] at the same time.
+ future: Mutex<WrappedFuture<F, T, UT>>,
+ scheduler: Mutex<Scheduler>,
+ // UT is used as the generic parameter for [LowerReturn].
+ // Let's model this with PhantomData as a function that inputs a UT value.
+ _phantom: PhantomData<fn(UT) -> ()>,
+}
+
+impl<F, T, UT> RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ pub(super) fn new(future: F, _tag: UT) -> Arc<Self> {
+ Arc::new(Self {
+ future: Mutex::new(WrappedFuture::new(future)),
+ scheduler: Mutex::new(Scheduler::new()),
+ _phantom: PhantomData,
+ })
+ }
+
+ pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) {
+ let ready = self.is_cancelled() || {
+ let mut locked = self.future.lock().unwrap();
+ let waker: std::task::Waker = Arc::clone(&self).into();
+ locked.poll(&mut Context::from_waker(&waker))
+ };
+ if ready {
+ callback(data, RustFuturePoll::Ready)
+ } else {
+ self.scheduler.lock().unwrap().store(callback, data);
+ }
+ }
+
+ pub(super) fn is_cancelled(&self) -> bool {
+ self.scheduler.lock().unwrap().is_cancelled()
+ }
+
+ pub(super) fn wake(&self) {
+ self.scheduler.lock().unwrap().wake();
+ }
+
+ pub(super) fn cancel(&self) {
+ self.scheduler.lock().unwrap().cancel();
+ }
+
+ pub(super) fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
+ self.future.lock().unwrap().complete(call_status)
+ }
+
+ pub(super) fn free(self: Arc<Self>) {
+ // Call cancel() to send any leftover data to the continuation callback
+ self.scheduler.lock().unwrap().cancel();
+ // Ensure we drop our inner future, releasing all held references
+ self.future.lock().unwrap().free();
+ }
+}
+
+impl<F, T, UT> Wake for RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ fn wake(self: Arc<Self>) {
+ self.deref().wake()
+ }
+
+ fn wake_by_ref(self: &Arc<Self>) {
+ self.deref().wake()
+ }
+}
+
+/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to
+/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this
+/// allows you to use RustFuture functionality without knowing the concrete Future type, which is
+/// unnamable.
+///
+/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of
+/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need
+/// to create a poll, cancel, complete, and free scaffolding function for each exported async
+/// function. That would add ~1kb binary size per exported function based on a quick estimate on a
+/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and
+/// only create those functions for each of the 13 possible FFI return types.
+#[doc(hidden)]
+pub trait RustFutureFfi<ReturnType>: Send + Sync {
+ fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64);
+ fn ffi_cancel(&self);
+ fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType;
+ fn ffi_free(self: Arc<Self>);
+}
+
+impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) {
+ self.poll(callback, data)
+ }
+
+ fn ffi_cancel(&self) {
+ self.cancel()
+ }
+
+ fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
+ self.complete(call_status)
+ }
+
+ fn ffi_free(self: Arc<Self>) {
+ self.free();
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs
new file mode 100644
index 0000000000..3d3505e5ef
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs
@@ -0,0 +1,141 @@
+/* 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/. */
+
+use std::{future::Future, sync::Arc};
+
+mod future;
+mod scheduler;
+use future::*;
+use scheduler::*;
+
+#[cfg(test)]
+mod tests;
+
+use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus};
+
+/// Result code for [rust_future_poll]. This is passed to the continuation function.
+#[repr(i8)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum RustFuturePoll {
+ /// The future is ready and is waiting for [rust_future_complete] to be called
+ Ready = 0,
+ /// The future might be ready and [rust_future_poll] should be called again
+ MaybeReady = 1,
+}
+
+/// Foreign callback that's passed to [rust_future_poll]
+///
+/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again
+/// to continue progress on the future.
+pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll);
+
+// === Public FFI API ===
+
+/// Create a new [Handle] for a Rust future
+///
+/// For each exported async function, UniFFI will create a scaffolding function that uses this to
+/// create the [Handle] to pass to the foreign code.
+pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> Handle
+where
+ // F is the future type returned by the exported async function. It needs to be Send + `static
+ // since it will move between threads for an indeterminate amount of time as the foreign
+ // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`,
+ // since we synchronize all access to the values.
+ F: Future<Output = T> + Send + 'static,
+ // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send +
+ // 'static for the same reason as F.
+ T: LowerReturn<UT> + Send + 'static,
+ // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy.
+ UT: Send + 'static,
+ // Needed to allocate a handle
+ dyn RustFutureFfi<T::ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle(
+ RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>
+ )
+}
+
+/// Poll a Rust future
+///
+/// When the future is ready to progress the continuation will be called with the `data` value and
+/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called
+/// exactly once.
+///
+/// # Safety
+///
+/// The [Handle] must not previously have been passed to [rust_future_free]
+pub unsafe fn rust_future_poll<ReturnType, UT>(
+ handle: Handle,
+ callback: RustFutureContinuationCallback,
+ data: u64,
+) where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_poll(callback, data)
+}
+
+/// Cancel a Rust future
+///
+/// Any current and future continuations will be immediately called with RustFuturePoll::Ready.
+///
+/// This is needed for languages like Swift, which continuation to wait for the continuation to be
+/// called when tasks are cancelled.
+///
+/// # Safety
+///
+/// The [Handle] must not previously have been passed to [rust_future_free]
+pub unsafe fn rust_future_cancel<ReturnType, UT>(handle: Handle)
+where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_cancel()
+}
+
+/// Complete a Rust future
+///
+/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for
+/// each supported FFI type.
+///
+/// # Safety
+///
+/// - The [Handle] must not previously have been passed to [rust_future_free]
+/// - The `T` param must correctly correspond to the [rust_future_new] call. It must
+/// be `<Output as LowerReturn<UT>>::ReturnType`
+pub unsafe fn rust_future_complete<ReturnType, UT>(
+ handle: Handle,
+ out_status: &mut RustCallStatus,
+) -> ReturnType
+where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_complete(out_status)
+}
+
+/// Free a Rust future, dropping the strong reference and releasing all references held by the
+/// future.
+///
+/// # Safety
+///
+/// The [Handle] must not previously have been passed to [rust_future_free]
+pub unsafe fn rust_future_free<ReturnType, UT>(handle: Handle)
+where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::consume_handle(handle).ffi_free()
+}
+
+// Derive HandleAlloc for dyn RustFutureFfi<T> for all FFI return types
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u8>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i8>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u16>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i16>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u32>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i32>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u64>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i64>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f32>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f64>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<*const std::ffi::c_void>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<crate::RustBuffer>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<()>);
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs
new file mode 100644
index 0000000000..629ee0c109
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs
@@ -0,0 +1,96 @@
+/* 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/. */
+
+use std::mem;
+
+use super::{RustFutureContinuationCallback, RustFuturePoll};
+
+/// Schedules a [crate::RustFuture] by managing the continuation data
+///
+/// This struct manages the continuation callback and data that comes from the foreign side. It
+/// is responsible for calling the continuation callback when the future is ready to be woken up.
+///
+/// The basic guarantees are:
+///
+/// * Each callback will be invoked exactly once, with its associated data.
+/// * If `wake()` is called, the callback will be invoked to wake up the future -- either
+/// immediately or the next time we get a callback.
+/// * If `cancel()` is called, the same will happen and the schedule will stay in the cancelled
+/// state, invoking any future callbacks as soon as they're stored.
+
+#[derive(Debug)]
+pub(super) enum Scheduler {
+ /// No continuations set, neither wake() nor cancel() called.
+ Empty,
+ /// `wake()` was called when there was no continuation set. The next time `store` is called,
+ /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady`
+ Waked,
+ /// The future has been cancelled, any future `store` calls should immediately result in the
+ /// continuation being called with `RustFuturePoll::Ready`.
+ Cancelled,
+ /// Continuation set, the next time `wake()` is called is called, we should invoke it.
+ Set(RustFutureContinuationCallback, u64),
+}
+
+impl Scheduler {
+ pub(super) fn new() -> Self {
+ Self::Empty
+ }
+
+ /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or
+ /// `Cancelled` state, call the continuation immediately with the data.
+ pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: u64) {
+ match self {
+ Self::Empty => *self = Self::Set(callback, data),
+ Self::Set(old_callback, old_data) => {
+ log::error!(
+ "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?"
+ );
+ old_callback(*old_data, RustFuturePoll::Ready);
+ *self = Self::Set(callback, data);
+ }
+ Self::Waked => {
+ *self = Self::Empty;
+ callback(data, RustFuturePoll::MaybeReady);
+ }
+ Self::Cancelled => {
+ callback(data, RustFuturePoll::Ready);
+ }
+ }
+ }
+
+ pub(super) fn wake(&mut self) {
+ match self {
+ // If we had a continuation set, then call it and transition to the `Empty` state.
+ Self::Set(callback, old_data) => {
+ let old_data = *old_data;
+ let callback = *callback;
+ *self = Self::Empty;
+ callback(old_data, RustFuturePoll::MaybeReady);
+ }
+ // If we were in the `Empty` state, then transition to `Waked`. The next time `store`
+ // is called, we will immediately call the continuation.
+ Self::Empty => *self = Self::Waked,
+ // This is a no-op if we were in the `Cancelled` or `Waked` state.
+ _ => (),
+ }
+ }
+
+ pub(super) fn cancel(&mut self) {
+ if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) {
+ callback(old_data, RustFuturePoll::Ready);
+ }
+ }
+
+ pub(super) fn is_cancelled(&self) -> bool {
+ matches!(self, Self::Cancelled)
+ }
+}
+
+// The `*const ()` data pointer references an object on the foreign side.
+// This object must be `Sync` in Rust terminology -- it must be safe for us to pass the pointer to the continuation callback from any thread.
+// If the foreign side upholds their side of the contract, then `Scheduler` is Send + Sync.
+
+unsafe impl Send for Scheduler {}
+unsafe impl Sync for Scheduler {}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs
new file mode 100644
index 0000000000..886ee27c71
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs
@@ -0,0 +1,223 @@
+use once_cell::sync::OnceCell;
+use std::{
+ future::Future,
+ panic,
+ pin::Pin,
+ sync::{Arc, Mutex},
+ task::{Context, Poll, Waker},
+};
+
+use super::*;
+use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode};
+
+// Sender/Receiver pair that we use for testing
+struct Channel {
+ result: Option<Result<String, TestError>>,
+ waker: Option<Waker>,
+}
+
+struct Sender(Arc<Mutex<Channel>>);
+
+impl Sender {
+ fn wake(&self) {
+ let inner = self.0.lock().unwrap();
+ if let Some(waker) = &inner.waker {
+ waker.wake_by_ref();
+ }
+ }
+
+ fn send(&self, value: Result<String, TestError>) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.result.replace(value).is_some() {
+ panic!("value already sent");
+ }
+ if let Some(waker) = &inner.waker {
+ waker.wake_by_ref();
+ }
+ }
+}
+
+struct Receiver(Arc<Mutex<Channel>>);
+
+impl Future for Receiver {
+ type Output = Result<String, TestError>;
+
+ fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<String, TestError>> {
+ let mut inner = self.0.lock().unwrap();
+ match &inner.result {
+ Some(v) => Poll::Ready(v.clone()),
+ None => {
+ inner.waker = Some(context.waker().clone());
+ Poll::Pending
+ }
+ }
+ }
+}
+
+// Create a sender and rust future that we can use for testing
+fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) {
+ let channel = Arc::new(Mutex::new(Channel {
+ result: None,
+ waker: None,
+ }));
+ let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag);
+ (Sender(channel), rust_future)
+}
+
+/// Poll a Rust future and get an OnceCell that's set when the continuation is called
+fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> {
+ let cell = Arc::new(OnceCell::new());
+ let handle = Arc::into_raw(cell.clone()) as u64;
+ rust_future.clone().ffi_poll(poll_continuation, handle);
+ cell
+}
+
+extern "C" fn poll_continuation(data: u64, code: RustFuturePoll) {
+ let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) };
+ cell.set(code).expect("Error setting OnceCell");
+}
+
+fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) {
+ let mut out_status_code = RustCallStatus::default();
+ let return_value = rust_future.ffi_complete(&mut out_status_code);
+ (return_value, out_status_code)
+}
+
+#[test]
+fn test_success() {
+ let (sender, rust_future) = channel();
+
+ // Test polling the rust future before it's ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ sender.wake();
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+
+ // Test polling the rust future when it's ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ sender.send(Ok("All done".into()));
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+
+ // Future polls should immediately return ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ // Complete the future
+ let (return_buf, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Success);
+ assert_eq!(
+ <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
+ "All done"
+ );
+}
+
+#[test]
+fn test_error() {
+ let (sender, rust_future) = channel();
+
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ sender.send(Err("Something went wrong".into()));
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ let (_, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Error);
+ unsafe {
+ assert_eq!(
+ <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer(
+ call_status.error_buf.assume_init()
+ )
+ .unwrap(),
+ TestError::from("Something went wrong"),
+ )
+ }
+}
+
+// Once `complete` is called, the inner future should be released, even if wakers still hold a
+// reference to the RustFuture
+#[test]
+fn test_cancel() {
+ let (_sender, rust_future) = channel();
+
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ rust_future.ffi_cancel();
+ // Cancellation should immediately invoke the callback with RustFuturePoll::Ready
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ // Future polls should immediately invoke the callback with RustFuturePoll::Ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ let (_, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Cancelled);
+}
+
+// Once `free` is called, the inner future should be released, even if wakers still hold a
+// reference to the RustFuture
+#[test]
+fn test_release_future() {
+ let (sender, rust_future) = channel();
+ // Create a weak reference to the channel to use to check if rust_future has dropped its
+ // future.
+ let channel_weak = Arc::downgrade(&sender.0);
+ drop(sender);
+ // Create an extra ref to rust_future, simulating a waker that still holds a reference to
+ // it
+ let rust_future2 = rust_future.clone();
+
+ // Complete the rust future
+ rust_future.ffi_free();
+ // Even though rust_future is still alive, the channel shouldn't be
+ assert!(Arc::strong_count(&rust_future2) > 0);
+ assert_eq!(channel_weak.strong_count(), 0);
+ assert!(channel_weak.upgrade().is_none());
+}
+
+// If `free` is called with a continuation still stored, we should call it them then.
+//
+// This shouldn't happen in practice, but it seems like good defensive programming
+#[test]
+fn test_complete_with_stored_continuation() {
+ let (_sender, rust_future) = channel();
+
+ let continuation_result = poll(&rust_future);
+ rust_future.ffi_free();
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+}
+
+// Test what happens if we see a `wake()` call while we're polling the future. This can
+// happen, for example, with futures that are handled by a tokio thread pool. We should
+// schedule another poll of the future in this case.
+#[test]
+fn test_wake_during_poll() {
+ let mut first_time = true;
+ let future = std::future::poll_fn(move |ctx| {
+ if first_time {
+ first_time = false;
+ // Wake the future while we are in the middle of polling it
+ ctx.waker().clone().wake();
+ Poll::Pending
+ } else {
+ // The second time we're polled, we're ready
+ Poll::Ready("All done".to_owned())
+ }
+ });
+ let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = RustFuture::new(future, crate::UniFfiTag);
+ let continuation_result = poll(&rust_future);
+ // The continuation function should called immediately
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+ // A second poll should finish the future
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+ let (return_buf, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Success);
+ assert_eq!(
+ <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
+ "All done"
+ );
+}
diff --git a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs
index af18f3873b..aec093154a 100644
--- a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs
+++ b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs
@@ -20,11 +20,11 @@
///
/// This crate needs to implement `FFIConverter<UT>` on `UniFfiTag` instances for all UniFFI
/// consumer crates. To do this, it defines blanket impls like `impl<UT> FFIConverter<UT> for u8`.
-/// "UT" means an abitrary `UniFfiTag` type.
+/// "UT" means an arbitrary `UniFfiTag` type.
use crate::{
check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata,
- ConvertError, FfiConverter, ForeignExecutor, Lift, LiftReturn, Lower, LowerReturn,
- MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError,
+ ConvertError, FfiConverter, Lift, LiftRef, LiftReturn, Lower, LowerReturn, MetadataBuffer,
+ Result, RustBuffer, UnexpectedUniFFICallbackError,
};
use anyhow::bail;
use bytes::buf::{Buf, BufMut};
@@ -405,47 +405,6 @@ where
.concat(V::TYPE_ID_META);
}
-/// FFI support for [ForeignExecutor]
-///
-/// These are passed over the FFI as opaque pointer-sized types representing the foreign executor.
-/// The foreign bindings may use an actual pointer to the executor object, or a usized integer
-/// handle.
-unsafe impl<UT> FfiConverter<UT> for ForeignExecutor {
- type FfiType = crate::ForeignExecutorHandle;
-
- // Passing these back to the foreign bindings is currently not supported
- fn lower(executor: Self) -> Self::FfiType {
- executor.handle
- }
-
- fn write(executor: Self, buf: &mut Vec<u8>) {
- // Use native endian when writing these values, so they can be casted to pointer values
- match std::mem::size_of::<usize>() {
- // Use native endian when reading these values, so they can be casted to pointer values
- 4 => buf.put_u32_ne(executor.handle.0 as u32),
- 8 => buf.put_u64_ne(executor.handle.0 as u64),
- n => panic!("Invalid usize width: {n}"),
- };
- }
-
- fn try_lift(executor: Self::FfiType) -> Result<Self> {
- Ok(ForeignExecutor::new(executor))
- }
-
- fn try_read(buf: &mut &[u8]) -> Result<Self> {
- let usize_val = match std::mem::size_of::<usize>() {
- // Use native endian when reading these values, so they can be casted to pointer values
- 4 => buf.get_u32_ne() as usize,
- 8 => buf.get_u64_ne() as usize,
- n => panic!("Invalid usize width: {n}"),
- };
- <Self as FfiConverter<UT>>::try_lift(crate::ForeignExecutorHandle(usize_val as *const ()))
- }
-
- const TYPE_ID_META: MetadataBuffer =
- MetadataBuffer::from_code(metadata::codes::TYPE_FOREIGN_EXECUTOR);
-}
-
derive_ffi_traits!(blanket u8);
derive_ffi_traits!(blanket i8);
derive_ffi_traits!(blanket u16);
@@ -460,7 +419,6 @@ derive_ffi_traits!(blanket bool);
derive_ffi_traits!(blanket String);
derive_ffi_traits!(blanket Duration);
derive_ffi_traits!(blanket SystemTime);
-derive_ffi_traits!(blanket ForeignExecutor);
// For composite types, derive LowerReturn, LiftReturn, etc, from Lift/Lower.
//
@@ -498,7 +456,11 @@ unsafe impl<UT> LowerReturn<UT> for () {
}
unsafe impl<UT> LiftReturn<UT> for () {
- fn lift_callback_return(_buf: RustBuffer) -> Self {}
+ type ReturnType = ();
+
+ fn try_lift_successful_return(_: ()) -> Result<Self> {
+ Ok(())
+ }
const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT);
}
@@ -535,13 +497,15 @@ where
unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E>
where
R: LiftReturn<UT>,
- E: Lift<UT> + ConvertError<UT>,
+ E: Lift<UT, FfiType = RustBuffer> + ConvertError<UT>,
{
- fn lift_callback_return(buf: RustBuffer) -> Self {
- Ok(R::lift_callback_return(buf))
+ type ReturnType = R::ReturnType;
+
+ fn try_lift_successful_return(v: R::ReturnType) -> Result<Self> {
+ R::try_lift_successful_return(v).map(Ok)
}
- fn lift_callback_error(buf: RustBuffer) -> Self {
+ fn lift_error(buf: RustBuffer) -> Self {
match E::try_lift_from_rust_buffer(buf) {
Ok(lifted_error) => Err(lifted_error),
Err(anyhow_error) => {
@@ -560,3 +524,14 @@ where
.concat(R::TYPE_ID_META)
.concat(E::TYPE_ID_META);
}
+
+unsafe impl<T, UT> LiftRef<UT> for [T]
+where
+ T: Lift<UT>,
+{
+ type LiftType = Vec<T>;
+}
+
+unsafe impl<UT> LiftRef<UT> for str {
+ type LiftType = String;
+}
diff --git a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs
index 3b5914e32f..4e7b9e06fa 100644
--- a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs
+++ b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs
@@ -51,7 +51,10 @@ use std::{borrow::Borrow, sync::Arc};
use anyhow::bail;
use bytes::Buf;
-use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError};
+use crate::{
+ FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode,
+ UnexpectedUniFFICallbackError,
+};
/// Generalized FFI conversions
///
@@ -302,14 +305,41 @@ pub unsafe trait LowerReturn<UT>: Sized {
/// These traits should not be used directly, only in generated code, and the generated code should
/// have fixture tests to test that everything works correctly together.
pub unsafe trait LiftReturn<UT>: Sized {
- /// Lift a Rust value for a callback interface method result
- fn lift_callback_return(buf: RustBuffer) -> Self;
+ /// FFI return type for trait interfaces
+ type ReturnType;
+
+ /// Lift a successfully returned value from a trait interface
+ fn try_lift_successful_return(v: Self::ReturnType) -> Result<Self>;
+
+ /// Lift a foreign returned value from a trait interface
+ ///
+ /// When we call a foreign-implemented trait interface method, we pass a &mut RustCallStatus
+ /// and get [Self::ReturnType] returned. This method takes both of those and lifts `Self` from
+ /// it.
+ fn lift_foreign_return(ffi_return: Self::ReturnType, call_status: RustCallStatus) -> Self {
+ match call_status.code {
+ RustCallStatusCode::Success => Self::try_lift_successful_return(ffi_return)
+ .unwrap_or_else(|e| {
+ Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e))
+ }),
+ RustCallStatusCode::Error => {
+ Self::lift_error(unsafe { call_status.error_buf.assume_init() })
+ }
+ _ => {
+ let e = <String as FfiConverter<crate::UniFfiTag>>::try_lift(unsafe {
+ call_status.error_buf.assume_init()
+ })
+ .unwrap_or_else(|e| format!("(Error lifting message: {e}"));
+ Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e))
+ }
+ }
+ }
/// Lift a Rust value for a callback interface method error result
///
/// This is called for "expected errors" -- the callback method returns a Result<> type and the
/// foreign code throws an exception that corresponds to the error type.
- fn lift_callback_error(_buf: RustBuffer) -> Self {
+ fn lift_error(_buf: RustBuffer) -> Self {
panic!("Callback interface method returned unexpected error")
}
@@ -351,6 +381,66 @@ pub trait ConvertError<UT>: Sized {
fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>;
}
+/// Manage handles for `Arc<Self>` instances
+///
+/// Handles are used to manage objects that are passed across the FFI. They general usage is:
+///
+/// * Rust creates an `Arc<>`
+/// * Rust uses `new_handle` to create a handle that represents the Arc reference
+/// * Rust passes the handle to the foreign code as a `u64`
+/// * The foreign code passes the handle back to `Rust` to refer to the object:
+/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an
+/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc`
+/// to get a new `Arc<>` clone for it.
+/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign
+/// code either stops using the handle after returning it or calls `clone_handle` and returns
+/// the clone.
+/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI
+/// function. This functions input an owned handle and consume it.
+///
+/// The foreign code also defines their own handles. These represent foreign objects that are
+/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse.
+///
+/// Handles must always be `Send` and the objects they reference must always be `Sync`.
+/// This means that it must be safe to send handles to other threads and use them there.
+///
+/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`.
+///
+/// ## Safety
+///
+/// All traits are unsafe (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.
+/// These traits should not be used directly, only in generated code, and the generated code should
+/// have fixture tests to test that everything works correctly together.
+/// `&T` using the Arc.
+pub unsafe trait HandleAlloc<UT>: Send + Sync {
+ /// Create a new handle for an Arc value
+ ///
+ /// Use this to lower an Arc into a handle value before passing it across the FFI.
+ /// The newly-created handle will have reference count = 1.
+ fn new_handle(value: Arc<Self>) -> Handle;
+
+ /// Clone a handle
+ ///
+ /// This creates a new handle from an existing one.
+ /// It's used when the foreign code wants to pass back an owned handle and still keep a copy
+ /// for themselves.
+ fn clone_handle(handle: Handle) -> Handle;
+
+ /// Get a clone of the `Arc<>` using a "borrowed" handle.
+ ///
+ /// Take care that the handle can not be destroyed between when it's passed and when
+ /// `get_arc()` is called. #1797 is a cautionary tale.
+ fn get_arc(handle: Handle) -> Arc<Self> {
+ Self::consume_handle(Self::clone_handle(handle))
+ }
+
+ /// Consume a handle, getting back the initial `Arc<>`
+ fn consume_handle(handle: Handle) -> Arc<Self>;
+}
+
/// Derive FFI traits
///
/// This can be used to derive:
@@ -439,9 +529,10 @@ macro_rules! derive_ffi_traits {
(impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => {
unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)*
{
- fn lift_callback_return(buf: $crate::RustBuffer) -> Self {
- <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf)
- .expect("Error reading callback interface result")
+ type ReturnType = <Self as $crate::Lift<$ut>>::FfiType;
+
+ fn try_lift_successful_return(v: Self::ReturnType) -> $crate::Result<Self> {
+ <Self as $crate::Lift<$ut>>::try_lift(v)
}
const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META;
@@ -463,4 +554,50 @@ macro_rules! derive_ffi_traits {
}
}
};
+
+ (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => {
+ // Derived HandleAlloc implementation.
+ //
+ // This is only needed for !Sized types like `dyn Trait`, below is a blanket implementation
+ // for any sized type.
+ unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)*
+ {
+ // To implement HandleAlloc for an unsized type, wrap it with a second Arc which
+ // converts the wide pointer into a normal pointer.
+
+ fn new_handle(value: ::std::sync::Arc<Self>) -> $crate::Handle {
+ $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value)))
+ }
+
+ fn clone_handle(handle: $crate::Handle) -> $crate::Handle {
+ unsafe {
+ ::std::sync::Arc::<::std::sync::Arc<Self>>::increment_strong_count(handle.as_pointer::<::std::sync::Arc<Self>>());
+ }
+ handle
+ }
+
+ fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc<Self> {
+ unsafe {
+ ::std::sync::Arc::<Self>::clone(
+ &std::sync::Arc::<::std::sync::Arc::<Self>>::from_raw(handle.as_pointer::<::std::sync::Arc<Self>>())
+ )
+ }
+ }
+ }
+ };
+}
+
+unsafe impl<T: Send + Sync, UT> HandleAlloc<UT> for T {
+ fn new_handle(value: Arc<Self>) -> Handle {
+ Handle::from_pointer(Arc::into_raw(value))
+ }
+
+ fn clone_handle(handle: Handle) -> Handle {
+ unsafe { Arc::increment_strong_count(handle.as_pointer::<T>()) };
+ handle
+ }
+
+ fn consume_handle(handle: Handle) -> Arc<Self> {
+ unsafe { Arc::from_raw(handle.as_pointer()) }
+ }
}
diff --git a/third_party/rust/uniffi_core/src/lib.rs b/third_party/rust/uniffi_core/src/lib.rs
index c84b403dce..1f3a2403f8 100644
--- a/third_party/rust/uniffi_core/src/lib.rs
+++ b/third_party/rust/uniffi_core/src/lib.rs
@@ -45,7 +45,8 @@ pub mod metadata;
pub use ffi::*;
pub use ffi_converter_traits::{
- ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn,
+ ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower,
+ LowerReturn,
};
pub use metadata::*;
@@ -57,9 +58,8 @@ pub mod deps {
pub use async_compat;
pub use bytes;
pub use log;
+ pub use oneshot;
pub use static_assertions;
- // Export this dependency for the 0.25 branch so that we can use it in `setup_scaffolding.rs`
- pub use once_cell;
}
mod panichook;
diff --git a/third_party/rust/uniffi_core/src/metadata.rs b/third_party/rust/uniffi_core/src/metadata.rs
index 770d2b36d5..dc61a1bfcb 100644
--- a/third_party/rust/uniffi_core/src/metadata.rs
+++ b/third_party/rust/uniffi_core/src/metadata.rs
@@ -32,13 +32,14 @@ pub mod codes {
pub const RECORD: u8 = 2;
pub const ENUM: u8 = 3;
pub const INTERFACE: u8 = 4;
- pub const ERROR: u8 = 5;
pub const NAMESPACE: u8 = 6;
pub const CONSTRUCTOR: u8 = 7;
pub const UDL_FILE: u8 = 8;
pub const CALLBACK_INTERFACE: u8 = 9;
pub const TRAIT_METHOD: u8 = 10;
pub const UNIFFI_TRAIT: u8 = 11;
+ pub const TRAIT_INTERFACE: u8 = 12;
+ pub const CALLBACK_TRAIT_INTERFACE: u8 = 13;
pub const UNKNOWN: u8 = 255;
// Type codes
@@ -66,20 +67,24 @@ pub mod codes {
pub const TYPE_CALLBACK_INTERFACE: u8 = 21;
pub const TYPE_CUSTOM: u8 = 22;
pub const TYPE_RESULT: u8 = 23;
- pub const TYPE_FUTURE: u8 = 24;
- pub const TYPE_FOREIGN_EXECUTOR: u8 = 25;
+ pub const TYPE_TRAIT_INTERFACE: u8 = 24;
+ pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25;
pub const TYPE_UNIT: u8 = 255;
- // Literal codes for LiteralMetadata - note that we don't support
- // all variants in the "emit/reader" context.
+ // Literal codes for LiteralMetadata
pub const LIT_STR: u8 = 0;
pub const LIT_INT: u8 = 1;
pub const LIT_FLOAT: u8 = 2;
pub const LIT_BOOL: u8 = 3;
- pub const LIT_NULL: u8 = 4;
+ pub const LIT_NONE: u8 = 4;
+ pub const LIT_SOME: u8 = 5;
+ pub const LIT_EMPTY_SEQ: u8 = 6;
}
-const BUF_SIZE: usize = 4096;
+// For large errors (e.g. enums) a buffer size of ~4k - ~8k
+// is not enough. See issues on Github: #1968 and #2041 and
+// for an example see fixture/large-error
+const BUF_SIZE: usize = 16384;
// This struct is a kludge around the fact that Rust const generic support doesn't quite handle our
// needs.
@@ -168,7 +173,17 @@ impl MetadataBuffer {
self.concat_value(value as u8)
}
- // Concatenate a string to this buffer.
+ // Option<bool>
+ pub const fn concat_option_bool(self, value: Option<bool>) -> Self {
+ self.concat_value(match value {
+ None => 0,
+ Some(false) => 1,
+ Some(true) => 2,
+ })
+ }
+
+ // Concatenate a string to this buffer. The maximum string length is 255 bytes. For longer strings,
+ // use `concat_long_str()`.
//
// Strings are encoded as a `u8` length, followed by the utf8 data.
//
@@ -189,6 +204,28 @@ impl MetadataBuffer {
self
}
+ // Concatenate a longer string to this buffer.
+ //
+ // Strings are encoded as a `u16` length, followed by the utf8 data.
+ //
+ // This consumes self, which is convenient for the proc-macro code and also allows us to avoid
+ // allocated an extra buffer.
+ pub const fn concat_long_str(mut self, string: &str) -> Self {
+ assert!(self.size + string.len() + 1 < BUF_SIZE);
+ let [lo, hi] = (string.len() as u16).to_le_bytes();
+ self.bytes[self.size] = lo;
+ self.bytes[self.size + 1] = hi;
+ self.size += 2;
+ let bytes = string.as_bytes();
+ let mut i = 0;
+ while i < bytes.len() {
+ self.bytes[self.size] = bytes[i];
+ self.size += 1;
+ i += 1;
+ }
+ self
+ }
+
// Create an array from this MetadataBuffer
//
// SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust