use std::{
future::Future,
marker::PhantomData,
ops::Deref,
pin::Pin,
task::{ready, Context, Poll},
};
use pin_project::pin_project;
pub struct Diversion<T, S> {
pub target: T,
pub store: Option<S>,
}
impl<T, S> From<T> for Diversion<T, S> {
fn from(value: T) -> Self {
Self {
target: value,
store: None,
}
}
}
#[must_use]
#[pin_project]
pub struct DiversionFuture<FU, T, S> {
#[pin]
inner: FU,
store: Option<S>,
_marker: PhantomData<T>,
}
impl<FU, T, S> DiversionFuture<FU, T, S> {
pub fn new(future: FU) -> Self {
Self {
inner: future,
store: None,
_marker: PhantomData,
}
}
pub fn store(&self) -> &Option<S> {
&self.store
}
}
impl<FU, T, S> Deref for DiversionFuture<FU, T, S> {
type Target = FU;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<FU, T, S, I> Future for DiversionFuture<FU, T, S>
where
FU: Future<Output = I>,
I: Into<Diversion<T, S>>,
{
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let Diversion { target, store } = ready!(this.inner.poll(cx)).into();
*this.store = store;
Poll::Ready(target)
}
}
#[cfg(test)]
mod tests {
use std::{future::poll_fn, pin::pin};
use super::*;
#[tokio::test]
async fn test_diversion_future() {
let mut f = pin!(DiversionFuture::new(async move {
Diversion {
target: "The answer to life, the universe, and everything.".to_string(),
store: Some(42),
}
},));
let question: String = poll_fn(|cx| f.as_mut().poll(cx)).await;
let answer = f.store().unwrap();
assert_eq!(
(question.as_str(), answer),
("The answer to life, the universe, and everything.", 42)
);
}
}