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}