future.rs |
[`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 |
13511 |
mod.rs |
|
5666 |
scheduler.rs |
|
4037 |
tests.rs |
|
9102 |