async_let/
lib.rs

1//! This crate implements async-let as a `#[no_std]`, no-unsafe, no-panic, minimal-overhead library.
2//!
3//! Async-let is an experiment based on Conrad Ludgate's musings from <https://conradludgate.com/posts/async-let>,
4//! which proposes a syntax that allows futures to be run "in the background" without spawning additional tasks.
5//! The general behavior of async-let is that the following (fictitious) syntax
6//!
7//! ```ignore
8//! async let metadata = client.request_metadata();
9//! ```
10//!
11//! will mark a future as a background future. Any await point between the binding and `data.await` will poll
12//! `data` as well as the future being awaited.
13//!
14//! ```ignore
15//! // `metadata` is marked as a background future here
16//! async let metadata = client.request_metadata();
17//!
18//! // both `data` and `metadata` are polled while awaiting `data`
19//! let data = client.request_data().await;
20//!
21//! // unmark `metadata` as a background future and await it
22//! let metadata = metadata.await;
23//! ```
24//!
25//! Using this crate's API, the above can be written like this.
26//!
27//! ```
28//! # use core::pin::pin;
29//! # struct Client;
30//! # impl Client {
31//! #   async fn request_metadata(&self) {}
32//! #   async fn request_data(&self) {}
33//! # }
34//! # let client = Client;
35//! # pollster::block_on(async move {
36//! // create a group that will drive the background futures
37//! let group = async_let::Group::new();
38//!
39//! // mark `metadata` as a background future by attaching it to the group
40//! let metadata = pin!(client.request_metadata());
41//! let (metadata, mut group) = group.attach(metadata);
42//!
43//! // await `data` using the group, which polls the background futures
44//! let data = group.wait_for(client.request_data()).await;
45//!
46//! // detach `metadata` from the group and await it
47//! let (metadata, group) = group.detach_and_wait_for(metadata).await;
48//! # });
49//! ```
50//!
51//! ## API
52//! The main types this crate offers are [`Group<List>`], a type that manages manages driving a statically typed
53//! `List` of background futures; and [`Handle<Fut>`], an abstract handle that represents the capability to extract
54//! a background future of type `Fut`. The [`attach`] method adds a future to the list of background futures, the
55//! [`detach`] method removes a future, and the [`wait_for`] method produces a future that will poll the background
56//! futures when it is polled.
57//!
58//! To minimize the overhead of tracking background futures, each `Group` is associated with a fixed set of futures.
59//! Attaching a future to a group or detaching a future from a group consumes the group and produces a new group with
60//! the future added to or removed from the set of background futures, respectively.
61//!
62//! ## Limitations
63//! In the quest for minimal overhead, several tradeoffs were made.
64//! - **Ergonomics**: while the API was made to be as ergonomic as possible, callers are still required to manually add and
65//!   remove futures, thread the group through each operation, and locally pin futures explicitly.
66//! - **Pinning**: Because a group stores its list of futures inline, the stored futures must be `Unpin`. Futures that require
67//!   pinning must be stored behind an indirection, such as with `pin!` or `Box::pin`.
68//! - **Lifetimes**: a group is necessarily moved when a future is attached or detached. If a future is attached in a nested
69//!   scope, it must be detached before leaving that scope, or else the group (and all its attached futures) will be made
70//!   inaccessible.
71//! - **Branching**: because the set of futures is statically tracked, it is not possible to attach a future in only one branch
72//!   of a condtional if one wishes the group to remain accessible after the conditional. Futures of different types may
73//!   be attached to the same location in a group by erasing the type of the attached future to `dyn Future<Output = X>`,
74//!   but this has its limitations.
75//!
76//! [`attach`]: Group::attach
77//! [`detach`]: Group::detach
78//! [`wait_for`]: Group::wait_for
79
80#![no_std]
81#![forbid(unsafe_code)]
82
83use core::{future::Future, marker::PhantomData};
84
85use list::{At, Detach, Empty, FutList};
86use wait::WaitFor;
87
88/// Types and traits for interacting with a group of futures.
89pub mod list;
90pub mod wait;
91
92/// A typed handle representing a specific future type in an async let group. A handle can be redeemed for the future
93/// it represents by passing it to [`Group::detach`].
94#[derive(Debug)]
95pub struct Handle<F> {
96    /// A handle logically replaces a future.
97    _ph: PhantomData<F>,
98}
99
100/// This type holds a future that has been detached from a group.
101#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
102pub enum ReadyOrNot<F: Future>
103{
104    /// If the future has run to completion, this variant holds the future's output.
105    Ready(F::Output),
106    /// If the future has not yet run to completion, this variant holds the future.
107    Not(F),
108}
109
110impl<F: Future> ReadyOrNot<F>
111{
112    /// A convenience method for retrieving the output of the future, either by driving the contained future
113    /// to completion or by unwrapping the output value.
114    ///
115    /// Note that this method does *not* drive the background futures of an async group. To drive the background
116    /// futures, use [`Group::detach_and_wait_for`] instead of detaching the future separately.
117    pub async fn output(self) -> F::Output {
118        match self {
119            ReadyOrNot::Ready(val) => val,
120            ReadyOrNot::Not(fut) => fut.await,
121        }
122    }
123}
124
125/// This type defines a specific set of futures that are driven whenever a future is awaited through a group's cooperation.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
127pub struct Group<List> {
128    /// The type-safe list of futures held in this group.
129    fut_list: List,
130}
131
132impl Group<Empty> {
133    /// Constructs a new group with no attached futures.
134    #[inline]
135    pub const fn new() -> Self {
136        Self {
137            fut_list: Empty { _priv: () },
138        }
139    }
140}
141
142impl<List> Group<List> {
143    /// Adds a future to this group's set of background futures. The future must be `Unpin`, or be pinned in external
144    /// storage before being attached.
145    ///
146    /// This method consumes `self` and returns a *new* group with the future attached, as well as a handle that
147    /// can be used to later detach the future.
148    ///
149    /// # Example
150    /// The most common way to use this method is to locally pin a future before attaching it.
151    /// ```
152    /// # use core::pin::pin;
153    /// # async fn some_future() {}
154    /// let group = async_let::Group::new();
155    ///
156    /// let fut = pin!(some_future()); // locally pin before attaching
157    /// let (handle, group) = group.attach(fut);
158    /// ```
159    /// However, any pinning pointer to a future can be used.
160    /// ```
161    /// # async fn some_future() {}
162    /// let group = async_let::Group::new();
163    ///
164    /// let mut fut = Box::pin(some_future()); // pin to the heap
165    /// let (handle, group) = group.attach(fut);
166    /// ```
167    pub fn attach<F: Future + Unpin>(self, fut: F) -> (Handle<F>, Group<At<F, List>>)
168    {
169        (
170            Handle { _ph: PhantomData },
171            Group {
172                fut_list: At {
173                    node: ReadyOrNot::Not(fut),
174                    tail: self.fut_list,
175                    _holds_output: PhantomData,
176                },
177            },
178        )
179    }
180
181    /// Removes a future from this group's set of background futures. The future held by this group
182    /// is relinquished and returned to the caller. The detached future may have been partially driven or even completed.
183    /// If the future is already completed, then its output is saved and returned to the caller instead of the future.
184    ///
185    /// This method consumes `self` and returns a *new* group with the future detached. If you want to additionally
186    /// await the detached future, you can use `Self::detach_and_wait_for` as a convenience.
187    ///
188    /// # Example
189    /// The primary reason for detaching a future *without* awaiting it is when its lifetime will end prior to it being
190    /// driven to completion.
191    /// ```
192    /// # use core::pin::pin;
193    /// # async fn some_future() {}
194    /// # async fn some_other_future() {}
195    /// # pollster::block_on(async {
196    /// let group = async_let::Group::new();
197    /// let mut group = {
198    ///     // attach a short-lived future
199    ///     let fut = pin!(some_future());
200    ///     let (handle, group) = group.attach(fut);
201    ///     
202    ///     // ... do work with `fut` in the background ...
203    ///
204    ///     // "error: temporary value dropped while borrowed" if commented
205    ///     let (fut, group) = group.detach(handle);
206    ///     group
207    /// };
208    /// // continue to use the group
209    /// let val = group.wait_for(some_other_future()).await;
210    /// # });
211    /// ```
212    /// # Type Inference
213    /// Usually, the future being detached is inferred from the provided handle. However, if multiple futures of the same
214    /// type are attached to the group, then type inference will not be able to determine *which* future should be detached.
215    /// In these cases, explicit disambiguation must be provided.
216    /// ```
217    /// # use core::pin::pin;
218    /// # async fn some_future() {}
219    /// let group = async_let::Group::new();
220    ///
221    /// let fut1 = pin!(some_future());
222    /// let (handle1, group) = group.attach(fut1);
223    ///
224    /// let fut2 = pin!(some_future());
225    /// let (handle2, group) = group.attach(fut2);
226    ///
227    /// // error: type annotations needed
228    /// // let (fut1, group) = group.detach(handle1);
229    /// use async_let::list::{S, Z};
230    /// let (fut1, group) = group.detach::<S<Z>, _>(handle1);
231    ///
232    /// // type inference *can* infer the index now that the other future is detached
233    /// let (fut2, group) = group.detach(handle2);
234    /// ```
235    pub fn detach<I, F: Future>(self, handle: Handle<F>) -> (ReadyOrNot<F>, Group<List::Output>)
236    where
237        List: Detach<F, I>,
238    {
239        let _ = handle;
240        let (fut, rest) = self.fut_list.detach();
241        (fut, Group { fut_list: rest })
242    }
243
244    /// Await a future while concurrently driving this group's background futures. The background futures are
245    /// not polled if the future being awaited does not suspend. The background futures share a context with
246    /// the future being awaited, so the enclosing task will be awoken if any one of the background futures makes progress.
247    ///
248    /// This method is used to await a future that is not in the set of background futures attached to this group.
249    /// To await a future that *is* attached to this group, use [`Self::detach_and_wait_for`].
250    ///
251    /// # Example
252    /// ```
253    /// # use core::pin::pin;
254    /// # pollster::block_on(async {
255    /// let group = async_let::Group::new();
256    ///
257    /// // ... attach futures to `group` ...
258    /// # let f1 = pin!(async {});
259    /// # let (h1, group) = group.attach(f1);
260    /// # let f2 = pin!(async {});
261    /// # let (h2, mut group) = group.attach(f2);
262    ///
263    /// let output = group.wait_for(async { 3 + 7 }).await;
264    /// assert_eq!(output, 10);
265    /// # });
266    /// ```
267    pub fn wait_for<F>(&mut self, fut: F) -> WaitFor<'_, F, List> {
268        WaitFor {
269            driving_fut: fut,
270            async_let_group: &mut self.fut_list,
271        }
272    }
273
274    /// A convenience method for [`detach`]ing a future followed by [`wait_for`] to get its output. If you do not
275    /// wish to drive the background futures while awaiting the output, use [`Group::detach`] followed by
276    /// [`ReadyOrNot::output`].
277    ///
278    /// # Example
279    /// ```
280    /// # use core::pin::pin;
281    /// # pollster::block_on(async {
282    /// # let group = async_let::Group::new();
283    /// let future = pin!(async { 3 + 7 });
284    /// let (handle, group) = group.attach(future);
285    ///
286    /// // ... do other work with `future` in the background ...
287    ///
288    /// let (output, group) = group.detach_and_wait_for(handle).await;
289    /// assert_eq!(output, 10);
290    /// # });
291    /// ```
292    /// [`detach`]: Self::detach
293    /// [`wait_for`]: Self::wait_for
294    pub async fn detach_and_wait_for<I, F: Future>(
295        self,
296        handle: Handle<F>,
297    ) -> (
298        F::Output,
299        Group<<List as Detach<F, I>>::Output>,
300    )
301    where
302        List: Detach<F, I>,
303        List::Output: FutList,
304    {
305        let (ready_or_not, mut group) = self.detach(handle);
306        let output = group.wait_for(ready_or_not.output()).await;
307        (output, group)
308    }
309}