Skip to main content

rune/runtime/
future.rs

1use core::fmt;
2use core::future;
3use core::pin::Pin;
4use core::ptr::NonNull;
5use core::task::{Context, Poll};
6
7use crate::alloc::alloc::Global;
8use crate::alloc::{self, Box};
9use crate::runtime::vm_execution::VmResumeOwned;
10use crate::runtime::{Dismantle, Handover, ToValue, Value, Vm, VmError, VmErrorKind, VmExecution};
11use crate::Any;
12
13use pin_project::pin_project;
14
15/// A virtual table for a type-erased future.
16struct Vtable {
17    poll: unsafe fn(*mut (), cx: &mut Context<'_>) -> Poll<Result<Value, VmError>>,
18    drop: unsafe fn(*mut ()),
19    /// Hand over the values the future holds, for the futures whose values can
20    /// be reached. `None` if they cannot be.
21    dismantle: Option<unsafe fn(*mut (), &mut Handover<'_>)>,
22    /// Test whether the future drives an execution which has yet to run.
23    ///
24    /// Set only for futures produced by an async call, since those are the ones
25    /// whose execution the awaiting machine can take over.
26    is_unstarted: Option<unsafe fn(*mut ()) -> bool>,
27    /// Take the machine out of an unstarted execution. Set alongside
28    /// `is_unstarted`, and only valid to call while it holds.
29    take_vm: Option<unsafe fn(*mut ()) -> Vm>,
30}
31
32/// A type-erased future that can only be unsafely polled in combination with
33/// the virtual machine that created it.
34#[derive(Any)]
35#[rune(crate)]
36#[rune(item = ::std::future, dismantle)]
37pub struct Future {
38    future: Option<NonNull<()>>,
39    vtable: &'static Vtable,
40}
41
42impl Future {
43    /// Construct a new wrapped future.
44    pub(crate) fn new<T, O>(future: T) -> alloc::Result<Self>
45    where
46        T: 'static + future::Future<Output = Result<O, VmError>>,
47        O: ToValue,
48    {
49        let (future, Global) = Box::into_raw_with_allocator(Box::try_new(future)?);
50
51        let future = unsafe { NonNull::new_unchecked(future).cast() };
52
53        Ok(Self {
54            future: Some(future),
55            vtable: &Vtable {
56                poll: |future, cx| unsafe {
57                    match Pin::new_unchecked(&mut *future.cast::<T>()).poll(cx) {
58                        Poll::Pending => Poll::Pending,
59                        Poll::Ready(result) => match result {
60                            Ok(result) => match result.to_value() {
61                                Ok(value) => Poll::Ready(Ok(value)),
62                                Err(err) => Poll::Ready(Err(err.into())),
63                            },
64                            Err(err) => Poll::Ready(Err(err)),
65                        },
66                    }
67                },
68                drop: |future| unsafe {
69                    _ = Box::from_raw_in(future.cast::<T>(), Global);
70                },
71                dismantle: None,
72                is_unstarted: None,
73                take_vm: None,
74            },
75        })
76    }
77
78    /// Construct a future which drives the given execution.
79    ///
80    /// This is what an async call produces rather than a future built out of an
81    /// `async` block, since the execution stays reachable through it. The
82    /// values a suspended execution holds can then be handed over instead of
83    /// being dropped in place, which is what keeps a chain of futures which
84    /// were never awaited from being dropped one frame per level.
85    pub(crate) fn from_execution(execution: VmExecution<Vm>) -> alloc::Result<Self> {
86        let future = Box::try_new(VmResumeOwned::new(execution))?;
87        let (future, Global) = Box::into_raw_with_allocator(future);
88
89        let future = unsafe { NonNull::new_unchecked(future).cast() };
90
91        Ok(Self {
92            future: Some(future),
93            vtable: &Vtable {
94                poll: |future, cx| unsafe {
95                    let future = Pin::new_unchecked(&mut *future.cast::<VmResumeOwned>());
96                    future::Future::poll(future, cx)
97                },
98                drop: |future| unsafe {
99                    _ = Box::from_raw_in(future.cast::<VmResumeOwned>(), Global);
100                },
101                dismantle: Some(|future, out| unsafe {
102                    (*future.cast::<VmResumeOwned>()).dismantle(out)
103                }),
104                is_unstarted: Some(|future| unsafe {
105                    (*future.cast::<VmResumeOwned>()).is_unstarted()
106                }),
107                take_vm: Some(|future| unsafe { (*future.cast::<VmResumeOwned>()).take_vm() }),
108            },
109        })
110    }
111
112    /// Take the machine out of this future, if it drives an execution which has
113    /// yet to run a single instruction.
114    ///
115    /// The future is left completed, since what it was driving has been handed
116    /// over. This is what lets a machine which awaits an async call splice that
117    /// call in as an ordinary call frame rather than driving a nested machine -
118    /// see [`Vm::splice_call`].
119    ///
120    /// [`Vm::splice_call`]: crate::runtime::Vm::splice_call
121    pub(crate) fn take_unstarted_vm(&mut self) -> Option<Vm> {
122        let (Some(future), Some(is_unstarted), Some(take_vm)) =
123            (self.future, self.vtable.is_unstarted, self.vtable.take_vm)
124        else {
125            return None;
126        };
127
128        // SAFETY: The future has not completed, so it is still live, and we
129        // hold it exclusively. Nothing borrows what it is made of in between
130        // polls.
131        unsafe {
132            if !is_unstarted(future.as_ptr()) {
133                return None;
134            }
135
136            let vm = take_vm(future.as_ptr());
137            self.future = None;
138            (self.vtable.drop)(future.as_ptr());
139            Some(vm)
140        }
141    }
142
143    /// Check if future is completed.
144    ///
145    /// This will prevent it from being used in a select expression.
146    pub fn is_completed(&self) -> bool {
147        self.future.is_none()
148    }
149}
150
151impl future::Future for Future {
152    type Output = Result<Value, VmError>;
153
154    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Value, VmError>> {
155        unsafe {
156            let this = self.get_unchecked_mut();
157
158            let Some(future) = this.future else {
159                return Poll::Ready(Err(VmError::new(VmErrorKind::FutureCompleted)));
160            };
161
162            match (this.vtable.poll)(future.as_ptr(), cx) {
163                Poll::Ready(result) => {
164                    this.future = None;
165                    (this.vtable.drop)(future.as_ptr());
166                    Poll::Ready(result)
167                }
168                Poll::Pending => Poll::Pending,
169            }
170        }
171    }
172}
173
174/// A future which was never awaited still holds everything the execution it
175/// drives was working over, so futures nest just like a container does.
176impl Dismantle for Future {
177    fn dismantle(&mut self, out: &mut Handover<'_>) {
178        let (Some(future), Some(dismantle)) = (self.future, self.vtable.dismantle) else {
179            return;
180        };
181
182        // SAFETY: As above.
183        unsafe { dismantle(future.as_ptr(), out) }
184    }
185}
186
187impl Drop for Future {
188    fn drop(&mut self) {
189        unsafe {
190            if let Some(future) = self.future.take() {
191                (self.vtable.drop)(future.as_ptr());
192            }
193        }
194    }
195}
196
197impl fmt::Debug for Future {
198    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
199        fmt.debug_struct("Future")
200            .field("is_completed", &self.future.is_none())
201            .finish_non_exhaustive()
202    }
203}
204
205/// Future wrapper used to keep track of associated data.
206#[pin_project]
207pub struct SelectFuture<T, F> {
208    data: T,
209    #[pin]
210    future: F,
211}
212
213impl<T, F> SelectFuture<T, F> {
214    /// Construct a new select future.
215    pub fn new(data: T, future: F) -> Self {
216        Self { data, future }
217    }
218}
219
220impl<T, F> future::Future for SelectFuture<T, F>
221where
222    T: Copy,
223    F: future::Future<Output = Result<Value, VmError>>,
224{
225    type Output = Result<(T, Value), VmError>;
226
227    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
228        let this = self.project();
229        let result = this.future.poll(cx);
230
231        match result {
232            Poll::Ready(result) => match result {
233                Ok(value) => Poll::Ready(Ok((*this.data, value))),
234                Err(error) => Poll::Ready(Err(error)),
235            },
236            Poll::Pending => Poll::Pending,
237        }
238    }
239}