std1.0.0-beta-7 SafeC docs

Module async::future

#include <std/async/future.h> source

async: futures, Poll, Waker, Context

Rust's async model without language-level coroutines. A future is a struct with two methods:

struct Poll<T> poll(&Context cx);   // make progress; Ready(value) or Pending
void           drop();              // abandon it (release timers, waiters)

poll() never blocks. When it can't finish yet it returns Poll::pending() — but only after arranging for cx's Waker to be woken once progress is possible again (a timer firing, a channel getting a value, a socket becoming readable). The executor then polls it again. Nothing runs a future except polling it: a future that is never polled does nothing, exactly like Rust's.

The pieces map one to one onto Rust's core::task / core::future:

Rust                         SafeC (std/async)
Poll<T>::Ready / Pending     struct Poll<T>: Poll::ready(v) / Poll::pending()
Future::poll(Pin<&mut Self>, &mut Context) -> Poll<Output>
                             struct Poll<T> F::poll(&Context cx)
Drop for a future            F::drop() (SafeC has no destructors)
Context, cx.waker()          struct Context, cx.waker / cx.wake_by_ref()
Waker (RawWaker + vtable)    struct Waker (type-erased behind Waker::new<T>)
async fn / .await            ASYNC_BEGIN / AWAIT / ASYNC_RETURN (std/async/coro.h)
Pin                          the rule below: a polled future doesn't move

Two rules follow from futures being plain structs:

* Pinning. Once polled, a future must stay where it is until it
  completes or is dropped — timers, channels and the I/O driver keep
  its address. Move it (into a combinator, an AsyncTask, ...) only
  before the first poll.
* Dropping. A future abandoned before it completes must be drop()ped:
  that is what unregisters it. Combinators (select, timeout) and
  executors (JoinHandle::abort) do this for the futures they own.
  drop() is idempotent and also fine after completion.

The Output type is the T of the Poll<T> that poll() returns. Futures that produce nothing return struct Poll<Unit> (Unit is an int; pass UNIT).

Structs

PollThe result of one poll: Ready with a value, or Pending (value unset).
WakerA handle that tells an executor "poll the task this belongs to again". It is a type-erased (data, functions) pair, like Rust's RawWaker and its vtable (see std/erased.h), made only through the generic constructors below, which take a matching fn(?&T, ...) and ?&T so every erased call is well-typed:
WakerSlotOne registered waker (Rust's AtomicWaker): where a single waiting future leaves its waker for whoever completes it. Not locked by itself; the structure that holds it (a channel, a task) guards it.
WaitList
TimerQueueThe executor's timer queue (implemented in timer.sc; Sleep, Interval and the queue's role are described in std/async/timer.h). Declared here because Context points at one.
ContextWhat poll() gets: the waker of the task being polled, and the services of the executor polling it. Executors fill it in for every poll; a future only reads it (and clones the waker if it needs to keep it).
ReadyReady with a value on the first poll (Rust's future::ready).
PendingNever ready (Rust's future::pending): useful in select/timeouts, tests.
YieldNowPending once (after waking itself), then Ready: gives the executor a chance to run other tasks (Rust's yield_now / tokio::task::yield_now).

Traits

FutureWhat a future is. The structural check is by name: a type with methods poll and drop fits, whatever its Output. Executors and combinators bound their future parameters with it, so a struct missing drop() is rejected where it is spawned or combined.

Type aliases

Unitthe output of a future that produces nothing: Rust's ()

Macros

UNIT
ASYNC_WAITERSA fixed set of registered wakers, for primitives several futures wait on at once (a channel's senders, a mutex's lockers). No heap: ASYNC_WAITERS slots. add() returns an id the future keeps and passes back to refresh or remove its registration; an id stays tied to its registration (a slot reused after a wake gets a new id), so a stale one is harmless. A waiter that finds the list full gets id 0 and must re-poll on its own (cx.wake_by_ref()) — slower, never wrong.