uniffi_core/ffi/rustfuture/mod.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::{future::Future, sync::Arc};
6
7mod future;
8mod scheduler;
9use future::*;
10use scheduler::*;
11
12#[cfg(test)]
13mod tests;
14
15use crate::{FfiDefault, Handle, LiftArgsError, LowerReturn, RustCallStatus};
16
17/// Result code for [rust_future_poll]. This is passed to the continuation function.
18#[repr(i8)]
19#[derive(Debug, PartialEq, Eq)]
20pub enum RustFuturePoll {
21 /// The future is ready and is waiting for [rust_future_complete] to be called
22 Ready = 0,
23 /// The future might be ready and [rust_future_poll] should be called again
24 Wake = 1,
25}
26
27/// Foreign callback that's passed to [rust_future_poll]
28///
29/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again
30/// to continue progress on the future.
31pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll);
32
33/// This marker trait allows us to put different bounds on the `Future`s we
34/// support, based on `#[cfg(..)]` configuration.
35///
36/// It should not be considered as a part of the public API, and as such as
37/// an implementation detail and subject to change.
38///
39/// It is _not_ intended to be implemented by library users or bindings
40/// implementers.
41#[doc(hidden)]
42#[cfg(not(all(target_arch = "wasm32", feature = "wasm-unstable-single-threaded")))]
43pub trait UniffiCompatibleFuture<T>: Future<Output = T> + Send {}
44
45#[doc(hidden)]
46pub trait FutureLowerReturn<UT>: LowerReturn<UT> {}
47
48/// The `Send` bound is required because the Foreign code may call the
49/// `rust_future_*` methods from different threads.
50#[cfg(not(target_arch = "wasm32"))]
51impl<T, F> UniffiCompatibleFuture<T> for F where F: Future<Output = T> + Send {}
52#[cfg(not(all(target_arch = "wasm32", feature = "wasm-unstable-single-threaded")))]
53impl<UT, LR> FutureLowerReturn<UT> for LR where LR: LowerReturn<UT> + Send {}
54
55/// `Future`'s on WASM32 are not `Send` because it's a single threaded environment.
56///
57/// # Safety:
58///
59/// WASM32 is a single threaded environment. However, in a browser there do
60/// exist [`WebWorker`][webworker]s which do not share memory or event-loop
61/// with the main browser context.
62///
63/// Communication between contexts is only possible by message passing,
64/// using a small number of ['transferable' object types][transferable].
65///
66/// The most common source of asynchrony in Rust compiled to WASM is
67/// [wasm-bindgen's `JsFuture`][jsfuture]. It is not `Send` because:
68///
69/// 1. `T` and `E` are both `JsValue`
70/// 2. `JsValue` may contain `JsFunction`s, either as a function themselves or
71/// an object containing functions.
72/// 3. Functions cannot be [serialized and sent][transferable] to `WebWorker`s.
73///
74/// Implementors of binding generators should be able to enumerate the
75/// combinations of Rust or JS communicating across different contexts (here
76/// using: <->), and in the same context (+) to account for why it is safe
77/// for UniFFI to support `Future`s that are not `Send`:
78///
79/// 1. JS + Rust in the same contexts: polling and waking happens in the same
80/// thread, no `Send` is needed.
81/// 2. Rust <-> Rust in different contexts: Futures cannot be sent between JS
82/// contexts within the same Rust crate (because they are not `Send`).
83/// 3. JS <-> Rust in different contexts: the `Promise` are [not transferable
84/// between contexts][transferable], so this is impossible.
85/// 4. JS <-> JS + Rust, this is possible, but safe since the Future is being
86/// driven by JS in the same thread. If a Promise metaphor is desired, then
87/// this must be built with JS talking to JS, because 3.
88///
89/// [webworker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
90/// [jsfuture]: https://github.com/rustwasm/wasm-bindgen/blob/main/crates/futures/src/lib.rs
91/// [transferable]: (https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects
92#[cfg(all(target_arch = "wasm32", feature = "wasm-unstable-single-threaded"))]
93pub trait UniffiCompatibleFuture<T>: Future<Output = T> {}
94
95#[cfg(target_arch = "wasm32")]
96impl<T, F> UniffiCompatibleFuture<T> for F where F: Future<Output = T> {}
97#[cfg(all(target_arch = "wasm32", feature = "wasm-unstable-single-threaded"))]
98impl<UT, LR> FutureLowerReturn<UT> for LR where LR: LowerReturn<UT> {}
99
100// === Public FFI API ===
101
102/// Create a new [Handle] for a Rust future
103///
104/// For each exported async function, UniFFI will create a scaffolding function that uses this to
105/// create the [Handle] to pass to the foreign code.
106// Need to allow let_and_return, or clippy complains when the `ffi-trace` feature is disabled.
107#[allow(clippy::let_and_return)]
108pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> Handle
109where
110 F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
111 T: FutureLowerReturn<UT> + 'static,
112{
113 let rust_future = Arc::new(RustFuture::new(future, tag));
114 let handle = Handle::from_arc(rust_future);
115 trace!("rust_future_new: {handle:?}");
116 handle
117}
118
119/// Poll a Rust future
120///
121/// When the future is ready to progress the continuation will be called with the `data` value and
122/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called
123/// exactly once.
124///
125/// # Safety
126///
127/// The [Handle] must not previously have been passed to [rust_future_free]
128pub unsafe fn rust_future_poll<FfiType>(
129 handle: Handle,
130 callback: RustFutureContinuationCallback,
131 data: u64,
132) {
133 trace!("rust_future_poll: {handle:?}");
134 Handle::into_arc_borrowed::<RustFuture<FfiType>>(handle).poll(callback, data)
135}
136
137/// Cancel a Rust future
138///
139/// Any current and future continuations will be immediately called with RustFuturePoll::Ready.
140///
141/// This is needed for languages like Swift, which continuation to wait for the continuation to be
142/// called when tasks are cancelled.
143///
144/// # Safety
145///
146/// The [Handle] must not previously have been passed to [rust_future_free]
147pub unsafe fn rust_future_cancel<FfiType>(handle: Handle) {
148 trace!("rust_future_cancel: {handle:?}");
149 Handle::into_arc_borrowed::<RustFuture<FfiType>>(handle).cancel()
150}
151
152/// Complete a Rust future
153///
154/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for
155/// each supported FFI type.
156///
157/// # Safety
158///
159/// - The [Handle] must not previously have been passed to [rust_future_free]
160/// - The `T` param must correctly correspond to the [rust_future_new] call. It must
161/// be `<Output as LowerReturn<UT>>::ReturnType`
162pub unsafe fn rust_future_complete<FfiType>(
163 handle: Handle,
164 out_status: &mut RustCallStatus,
165) -> FfiType
166where
167 FfiType: FfiDefault,
168{
169 trace!("rust_future_complete: {handle:?}");
170 Handle::into_arc_borrowed::<RustFuture<FfiType>>(handle).complete(out_status)
171}
172
173/// Free a Rust future, dropping the strong reference and releasing all references held by the
174/// future.
175///
176/// # Safety
177///
178/// The [Handle] must not previously have been passed to [rust_future_free]
179pub unsafe fn rust_future_free<FfiType>(handle: Handle) {
180 trace!("rust_future_free: {handle:?}");
181 Handle::into_arc_borrowed::<RustFuture<FfiType>>(handle).free()
182}