D   A   T   A   W   O   K





Creation: March 29 2021
Modified: August 12 2021

Asynchronous Rust

Futures

Futures need to be polled to completion. This is the job of an executor.

Some futures may need to wait for events from the kernel, for example to know when there might be data ready to read from a network channel.

A reactor handles this (by using mio or poll) to register for events from the kernel and know when things might be able to progress.

Main components

std::future has the core futures functionality needed to implement async/await syntax.

Future trait

std::future::Future is a trait representing an asyncronous computation.

The core method of future, poll, attemps to resolve the future into a final value. This method does not block if the value is not ready. Instead, the current task is scheduled to be woken up when it's possible to make further progress by polling again.

The poll function returns:

When the future has finished, clients should not poll again.

When using a future, you generally won't poll directy, but instead .await the value.

Waker

std::task::Waker is a handle for waking up a task by notifying its executor that it is ready to be run (implements Clone, Send and Sync).

The context passed to the Future::poll method can provide a Waker for waking up the current task.

When future is not ready yet, poll returns Poll::Pending and stores a clone of the Waker copied from the current Context. This Waker is then woken-up once the future can make progress.

Runtime

Futures alone are inert; they must be actively polled to make progress, meaning that each time the current task is woken up, it should actively re-poll pending futures that it still has interest in.

Runtimes are not included in the standard library and is typically provided by an external crate.

Futures crate

futures-rs is a crate which adds utility and abstraction over futures: FutureExt, TryFutureExt, Stream, StreamExt, TryStreamExt, Sink, SinkExt.

Tokio crate

Tokio brings an async runtime and some additional utility to interact with environment in async way: IO, time, unix signals, sync primitives.

Tokio is built over futures-rs.

Has an executor and a reactor bundled within it.

Futures that rely on the tokio::io/fs need to be run inside the context of a tokio runtime (which makes the tokio reactor available to them and allows spawning).

Async-Std

Brings an API very similar to standard lib, but for async programming. Somehow like futures-rs and tokio merged together.

Uses a different executor than tokio.

Smol

A small and fast async runtime implemented around async-executor.

Doesn't come with a reactor itself.

Has a compatibility layer to interact withh tokio-based libraries.


Q&A

Why Waker is not implemented as a trait object?

If you make Waker a trait, then you must use it as a trait object, because if the Future trait is generic over the kind of waker, then it becomes non-object safe, making Box<dyn Future> impossible.

If you make it an ordinary trait object, then what should clone return? It can't return Self, as that makes the Waker trait non-object safe, so it must return a trait object. However returning a trait object is not possible without allocating, and we want async/await to be feasible on embedded systems without allocations.

proudly self-hosted on a cheap Raspberry Pi 2