Skip to content

Commit

Permalink
✨ (future): Introduce FutureExt::with_cancel_signal() and `WithCanc…
Browse files Browse the repository at this point in the history
…elSignal` Future
  • Loading branch information
czy-29 committed Jan 27, 2025
1 parent 4d5b24e commit 43c398d
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 0 deletions.
1 change: 1 addition & 0 deletions README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
部分条目如下:
- [`AnyRes`](https://docs.rs/est/latest/est/result/type.AnyRes.html)
- [`collections::MapExt::replace_key()`](https://docs.rs/est/latest/est/collections/trait.MapExt.html#tymethod.replace_key)
- [`future::FutureExt::with_cancel_signal()`](https://docs.rs/est/latest/est/future/trait.FutureExt.html#tymethod.with_cancel_signal)
- [`sync::once`](https://docs.rs/est/latest/est/sync/once/index.html)
- [`task::CloseAndWait::close_and_wait()`](https://docs.rs/est/latest/est/task/trait.CloseAndWait.html#tymethod.close_and_wait)
- [`task::TaskId`](https://docs.rs/est/latest/est/task/struct.TaskId.html)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Latest version: [v0.7.1](https://github.com/opensound-org/est/releases/tag/v0.7.
Some of the items are as follows:
- [`AnyRes`](https://docs.rs/est/latest/est/result/type.AnyRes.html)
- [`collections::MapExt::replace_key()`](https://docs.rs/est/latest/est/collections/trait.MapExt.html#tymethod.replace_key)
- [`future::FutureExt::with_cancel_signal()`](https://docs.rs/est/latest/est/future/trait.FutureExt.html#tymethod.with_cancel_signal)
- [`sync::once`](https://docs.rs/est/latest/est/sync/once/index.html)
- [`task::CloseAndWait::close_and_wait()`](https://docs.rs/est/latest/est/task/trait.CloseAndWait.html#tymethod.close_and_wait)
- [`task::TaskId`](https://docs.rs/est/latest/est/task/struct.TaskId.html)
Expand Down
98 changes: 98 additions & 0 deletions src/future.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};

/// A `Future` that can `select` whether a `Future` is successfully completed or cancelled
/// by a cancellation signal.
///
/// Use [`FutureExt::with_cancel_signal`] to construct.
///
/// If the execution of the original `Future` is completed, `.await` will resolve to `Ok`
/// with the `Output` of the original `Future`; otherwise (if cancelled by the cancellation
/// signal halfway), `.await` will resolve to `Err` with the `Output` of the cancellation
/// signal `Future`.
///
/// Note: This `Future` will acquire the ownership of these two `Future`s, and [`Box::pin`]
/// them. This allows the two `Future`s to be arbitrary, including those that are not
/// [`Unpin`] (such as those generated by the `async block`).
#[derive(Debug)]
pub struct WithCancelSignal<F: Future, C: Future> {
future: Pin<Box<F>>,
cancel: Pin<Box<C>>,
}

impl<F, C> Future for WithCancelSignal<F, C>
where
F: Future,
C: Future,
{
type Output = Result<F::Output, C::Output>;

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Poll::Ready(o) = Pin::new(&mut self.future).poll(cx) {
return Poll::Ready(Ok(o));
}

if let Poll::Ready(o) = Pin::new(&mut self.cancel).poll(cx) {
return Poll::Ready(Err(o));
}

Poll::Pending
}
}

/// [`Future`] extension trait.
///
/// This trait has been implemented for all [`Sized`] `Future`s.
pub trait FutureExt: Future + Sized {
/// Construct a [`WithCancelSignal`] Future that can be selected and resolved to
/// [`Result`] according to "whether it is cancelled by a cancellation signal".
///
/// # Example
///
/// ```
/// use est::future::FutureExt;
/// use std::time::Duration;
/// use tokio::time::sleep;
///
/// #[tokio::main]
/// async fn main() {
/// let future = sleep(Duration::from_millis(100));
/// let cancel = sleep(Duration::from_millis(50));
/// assert!(future.with_cancel_signal(cancel).await.is_err());
///
/// let future = sleep(Duration::from_millis(100));
/// let cancel = sleep(Duration::from_millis(200));
/// assert!(future.with_cancel_signal(cancel).await.is_ok());
/// }
/// ```
fn with_cancel_signal<C: Future>(self, cancel: C) -> WithCancelSignal<Self, C> {
WithCancelSignal {
future: Box::pin(self),
cancel: Box::pin(cancel),
}
}
}

impl<T: Future + Sized> FutureExt for T {}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn with_cancel_signal() {
use std::time::Duration;
use tokio::time::sleep;

let cancel = async move { sleep(Duration::from_millis(100)).await };
let future = async move { sleep(Duration::from_millis(200)).await };
assert!(future.with_cancel_signal(cancel).await.is_err());

let cancel = async move { sleep(Duration::from_millis(100)).await };
let future = async move { sleep(Duration::from_millis(50)).await };
assert!(future.with_cancel_signal(cancel).await.is_ok());
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
/// Extensions to the [`std::collections`](https://doc.rust-lang.org/stable/std/collections/index.html) module.
pub mod collections;
/// Extensions to the [`std::future`](https://doc.rust-lang.org/stable/std/future/index.html) module.
pub mod future;
/// Extensions to the [`std::result`](https://doc.rust-lang.org/stable/std/result/index.html) module.
pub mod result;
/// Extensions to the [`std::sync`](https://doc.rust-lang.org/stable/std/sync/index.html) &
Expand Down

0 comments on commit 43c398d

Please sign in to comment.