genawaiter/stack/
generator.rs

1use crate::{
2    core::{advance, async_advance, Airlock as _, Next},
3    ext::MaybeUninitExt,
4    ops::{Coroutine, GeneratorState},
5    stack::engine::{Airlock, Co},
6};
7use std::{future::Future, mem, pin::Pin, ptr};
8
9/// This data structure holds the transient state of an executing generator.
10///
11/// It's called "Shelf", rather than "State", to avoid confusion with the
12/// `GeneratorState` enum.
13///
14/// [_See the module-level docs for examples._](.)
15// Safety: The lifetime of the data is controlled by a `Gen`, which constructs
16// it in place, and holds a mutable reference right up until dropping it in
17// place. Thus, the data inside is pinned and can never be moved.
18pub struct Shelf<Y, R, F: Future>(mem::MaybeUninit<State<Y, R, F>>);
19
20struct State<Y, R, F: Future> {
21    airlock: Airlock<Y, R>,
22    future: F,
23}
24
25impl<Y, R, F: Future> Shelf<Y, R, F> {
26    /// Creates a new, empty `Shelf`.
27    ///
28    /// [_See the module-level docs for examples._](.)
29    #[must_use]
30    pub fn new() -> Self {
31        Self(mem::MaybeUninit::uninit())
32    }
33}
34
35impl<Y, R, F: Future> Default for Shelf<Y, R, F> {
36    #[must_use]
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42/// This is a generator which can be stack-allocated.
43///
44/// [_See the module-level docs for examples._](.)
45pub struct Gen<'s, Y, R, F: Future> {
46    state: Pin<&'s mut State<Y, R, F>>,
47}
48
49impl<'s, Y, R, F: Future> Gen<'s, Y, R, F> {
50    /// Creates a new generator from a function.
51    ///
52    /// The state of the generator is stored in `shelf`, which will be pinned in
53    /// place while this generator exists. The generator itself is movable,
54    /// since it just holds a reference to the pinned state.
55    ///
56    /// The function accepts a [`Co`] object, and returns a future. Every time
57    /// the generator is resumed, the future is polled. Each time the future is
58    /// polled, it should do one of two things:
59    ///
60    /// - Call `co.yield_()`, and then return `Poll::Pending`.
61    /// - Drop the `Co`, and then return `Poll::Ready`.
62    ///
63    /// Typically this exchange will happen in the context of an `async fn`.
64    ///
65    /// # Safety
66    ///
67    /// The `Co` object must not outlive the returned `Gen`. By time the
68    /// generator completes (i.e., by time the producer's Future returns
69    /// `Poll::Ready`), the `Co` object should already have been dropped. If
70    /// this invariant is not upheld, memory unsafety can result.
71    ///
72    /// Afaik, the Rust compiler [is not flexible enough][hrtb-thread] to let
73    /// you express this invariant in the type system, but I would love to be
74    /// proven wrong!
75    ///
76    /// [hrtb-thread]: https://users.rust-lang.org/t/hrtb-on-multiple-generics/34255
77    ///
78    /// # Examples
79    ///
80    /// ```rust
81    /// # use genawaiter::stack::{Co, Gen, Shelf};
82    /// #
83    /// # async fn producer(co: Co<'_, i32>) { /* ... */ }
84    /// #
85    /// let mut shelf = Shelf::new();
86    /// let gen = unsafe { Gen::new(&mut shelf, producer) };
87    /// ```
88    pub unsafe fn new(
89        shelf: &'s mut Shelf<Y, R, F>,
90        producer: impl FnOnce(Co<'s, Y, R>) -> F,
91    ) -> Self {
92        // Safety: Build the struct in place, by writing each field in place.
93        let p = &mut *shelf.0.as_mut_ptr() as *mut State<Y, R, F>;
94
95        let airlock = Airlock::default();
96        ptr::write(&mut (*p).airlock, airlock);
97
98        let future = producer(Co::new(&(*p).airlock));
99        ptr::write(&mut (*p).future, future);
100
101        // Safety: the state can never be moved again, because we store it inside a
102        // `Pin` until `Gen::drop`, where the contents are dropped in place.
103        let state = Pin::new_unchecked(shelf.0.assume_init_get_mut());
104        Self { state }
105    }
106
107    /// Resumes execution of the generator.
108    ///
109    /// `arg` is the resume argument. If the generator was previously paused by
110    /// awaiting a future returned from `co.yield()`, that future will complete,
111    /// and return `arg`.
112    ///
113    /// If the generator yields a value, `Yielded` is returned. Otherwise,
114    /// `Completed` is returned.
115    ///
116    /// [_See the module-level docs for examples._](.)
117    pub fn resume_with(&mut self, arg: R) -> GeneratorState<Y, F::Output> {
118        let (future, airlock) = self.project();
119        airlock.replace(Next::Resume(arg));
120        advance(future, &airlock)
121    }
122
123    fn project(&mut self) -> (Pin<&mut F>, &Airlock<Y, R>) {
124        unsafe {
125            // Safety: This is a pin projection. `future` is pinned, but never moved.
126            // `airlock` is never pinned.
127            let state = self.state.as_mut().get_unchecked_mut();
128
129            let future = Pin::new_unchecked(&mut state.future);
130            let airlock = &state.airlock;
131            (future, airlock)
132        }
133    }
134}
135
136impl<'s, Y, R, F: Future> Drop for Gen<'s, Y, R, F> {
137    fn drop(&mut self) {
138        // Safety: `state` is a `MaybeUninit` which is guaranteed to be initialized,
139        // because the only way to construct a `Gen` is with `Gen::new`, which
140        // initializes it.
141        //
142        // Drop `state` in place, by dropping each field in place. Drop `future` first,
143        // since it likely contains a reference to `airlock` (through the `co` object).
144        // Since we drop everything in place, the `Pin` invariants are not violated.
145        unsafe {
146            let state = self.state.as_mut().get_unchecked_mut();
147            ptr::drop_in_place(&mut state.future);
148            ptr::drop_in_place(&mut state.airlock);
149        }
150    }
151}
152
153impl<'s, Y, F: Future> Gen<'s, Y, (), F> {
154    /// Resumes execution of the generator.
155    ///
156    /// If the generator yields a value, `Yielded` is returned. Otherwise,
157    /// `Completed` is returned.
158    ///
159    /// [_See the module-level docs for examples._](.)
160    pub fn resume(&mut self) -> GeneratorState<Y, F::Output> {
161        self.resume_with(())
162    }
163
164    /// Resumes execution of the generator.
165    ///
166    /// If the generator pauses without yielding, `Poll::Pending` is returned.
167    /// If the generator yields a value, `Poll::Ready(Yielded)` is returned.
168    /// Otherwise, `Poll::Ready(Completed)` is returned.
169    ///
170    /// [_See the module-level docs for examples._](.)
171    pub fn async_resume(
172        &mut self,
173    ) -> impl Future<Output = GeneratorState<Y, F::Output>> + '_ {
174        let (future, airlock) = self.project();
175        airlock.replace(Next::Resume(()));
176        async_advance(future, airlock)
177    }
178}
179
180impl<'s, Y, R, F: Future> Coroutine for Gen<'s, Y, R, F> {
181    type Yield = Y;
182    type Resume = R;
183    type Return = F::Output;
184
185    fn resume_with(
186        self: Pin<&mut Self>,
187        arg: R,
188    ) -> GeneratorState<Self::Yield, Self::Return> {
189        // Safety: `Gen::resume_with` does not move `self`.
190        let this = unsafe { self.get_unchecked_mut() };
191        this.resume_with(arg)
192    }
193}