Expand description
[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:
- At startup, register a RustFutureContinuationCallback by calling rust_future_continuation_callback_set.
- 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.
- Call rust_future_complete to get the result of the future.
- 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)
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
.
Structs§
- Opaque handle for a Rust future that’s stored by the foreign language code
Enums§
- Result code for rust_future_poll. This is passed to the continuation function.
Functions§
- Cancel a Rust future
- Complete a Rust future
- Free a Rust future, dropping the strong reference and releasing all references held by the future.
- Create a new RustFutureHandle
- Poll a Rust future
Type Aliases§
- Foreign callback that’s passed to rust_future_poll