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}