1 //! Definition of the [`MaybeDone`] combinator.
2 
3 use pin_project_lite::pin_project;
4 use std::future::{Future, IntoFuture};
5 use std::pin::Pin;
6 use std::task::{ready, Context, Poll};
7 
8 pin_project! {
9     /// A future that may have completed.
10     #[derive(Debug)]
11     #[project = MaybeDoneProj]
12     #[project_replace = MaybeDoneProjReplace]
13     #[repr(C)] // https://github.com/rust-lang/miri/issues/3780
14     pub enum MaybeDone<Fut: Future> {
15         /// A not-yet-completed future.
16         Future { #[pin] future: Fut },
17         /// The output of the completed future.
18         Done { output: Fut::Output },
19         /// The empty variant after the result of a [`MaybeDone`] has been
20         /// taken using the [`take_output`](MaybeDone::take_output) method.
21         Gone,
22     }
23 }
24 
25 /// Wraps a future into a `MaybeDone`.
maybe_done<F: IntoFuture>(future: F) -> MaybeDone<F::IntoFuture>26 pub fn maybe_done<F: IntoFuture>(future: F) -> MaybeDone<F::IntoFuture> {
27     MaybeDone::Future {
28         future: future.into_future(),
29     }
30 }
31 
32 impl<Fut: Future> MaybeDone<Fut> {
33     /// Returns an [`Option`] containing a mutable reference to the output of the future.
34     /// The output of this method will be [`Some`] if and only if the inner
35     /// future has been completed and [`take_output`](MaybeDone::take_output)
36     /// has not yet been called.
output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Output>37     pub fn output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Output> {
38         match self.project() {
39             MaybeDoneProj::Done { output } => Some(output),
40             _ => None,
41         }
42     }
43 
44     /// Attempts to take the output of a `MaybeDone` without driving it
45     /// towards completion.
46     #[inline]
take_output(self: Pin<&mut Self>) -> Option<Fut::Output>47     pub fn take_output(self: Pin<&mut Self>) -> Option<Fut::Output> {
48         match *self {
49             MaybeDone::Done { .. } => {}
50             MaybeDone::Future { .. } | MaybeDone::Gone => return None,
51         };
52         if let MaybeDoneProjReplace::Done { output } = self.project_replace(MaybeDone::Gone) {
53             Some(output)
54         } else {
55             unreachable!()
56         }
57     }
58 }
59 
60 impl<Fut: Future> Future for MaybeDone<Fut> {
61     type Output = ();
62 
poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>63     fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
64         let output = match self.as_mut().project() {
65             MaybeDoneProj::Future { future } => ready!(future.poll(cx)),
66             MaybeDoneProj::Done { .. } => return Poll::Ready(()),
67             MaybeDoneProj::Gone => panic!("MaybeDone polled after value taken"),
68         };
69         self.set(MaybeDone::Done { output });
70         Poll::Ready(())
71     }
72 }
73 
74 // Test for https://github.com/tokio-rs/tokio/issues/6729
75 #[cfg(test)]
76 mod miri_tests {
77     use super::maybe_done;
78 
79     use std::{
80         future::Future,
81         pin::Pin,
82         sync::Arc,
83         task::{Context, Poll, Wake},
84     };
85 
86     struct ThingAdder<'a> {
87         thing: &'a mut String,
88     }
89 
90     impl Future for ThingAdder<'_> {
91         type Output = ();
92 
poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output>93         fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
94             unsafe {
95                 *self.get_unchecked_mut().thing += ", world";
96             }
97             Poll::Pending
98         }
99     }
100 
101     #[test]
maybe_done_miri()102     fn maybe_done_miri() {
103         let mut thing = "hello".to_owned();
104 
105         // The async block is necessary to trigger the miri failure.
106         #[allow(clippy::redundant_async_block)]
107         let fut = async move { ThingAdder { thing: &mut thing }.await };
108 
109         let mut fut = maybe_done(fut);
110         let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
111 
112         let waker = Arc::new(DummyWaker).into();
113         let mut ctx = Context::from_waker(&waker);
114         assert_eq!(fut.as_mut().poll(&mut ctx), Poll::Pending);
115         assert_eq!(fut.as_mut().poll(&mut ctx), Poll::Pending);
116     }
117 
118     struct DummyWaker;
119 
120     impl Wake for DummyWaker {
wake(self: Arc<Self>)121         fn wake(self: Arc<Self>) {}
122     }
123 }
124