Promise / Futures
uniffi-bindgen-react-native provides support of Futures/async fn. These are mapped to Javascript Promises. More information can be found in the uniffi book.
This example is taken from the above link:
#![allow(unused)] fn main() { use std::time::Duration; use async_std::future::{timeout, pending}; /// Async function that says something after a certain time. #[uniffi::export] pub async fn say_after(ms: u64, who: String) -> String { let never = pending::<()>(); timeout(Duration::from_millis(ms), never).await.unwrap_err(); format!("Hello, {who}!") } }
It can be called from Typescript:
// Wait 1 second for Hello, World!
const message = await sayAfter(1000n, "World");
You can see this in action in the futures-example example, and the more complete futures fixture.
Promises from synchronous Rust
Rust that is not async can still be given a Promise surface in Typescript, with the forceAsync option in uniffi.toml. The call itself stays synchronous; only the signature changes. It is a migration aid — a way to get call sites into the shape that calling Rust off the main thread requires, without changing what the code does today.
Passing Promises across the FFI
There is no support for passing a Promise or Future as an argument or error, in either direction.
Task cancellation
Internally, uniffi-rs generates a cancel function for each Future. On calling it, the Future is dropped.
This is accessible for every function that returns a Promise by passing an optional { signal: AbortSignal } option bag as the final argument.
Using the same Rust as above:
#![allow(unused)] fn main() { use std::time::Duration; use async_std::future::{timeout, pending}; /// Async function that says something after a certain time. #[uniffi::export] pub async fn say_after(ms: u64, who: String) -> String { let never = pending::<()>(); timeout(Duration::from_millis(ms), never).await.unwrap_err(); format!("Hello, {who}!") } }
It can be used from Typescript, either without an AbortSignal as above, or with one passed as the final argument:
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 1000);
try {
// Wait 1 hour for Hello, World!
const message = await sayAfter(60 * 60 * 1000, "World", { signal: abortController.signal });
console.log(message);
} catch (e: any) {
e instanceof Error; // true
e.name === "AbortError"; // true
}
This example calls into the say_after function, and the Rust would wait for 1 hour before returning. However, the abortController has its abort method called after 1 second.
Task cancellation for one language is… complicated. For FFIs, it is a small but important source of impedence mismatches between languages.
The uniffi-rs docs suggest that:
You should build your cancellation in a separate, library specific channel; for example, exposing a
cancel()method that sets a flag that the library checks periodically.
While uniffi-rs is recommending this, uniffi-bindgen-react-native— as a foreign-language binding to the uniffi-rs code— does so too.
However, while uniffi-rs exposes rust_future_cancel function, uniffi-bindgen-react-native— as a foreign-language binding to the uniffi-rs code— does so too.