From 13ab54f55d3ffb96f65c31c1f0e4d9fdeb60f2b3 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Sat, 3 Feb 2024 16:53:44 -0500 Subject: [PATCH] Rewrite the entire thing, add docs The new implementation has fewer surprises! --- Cargo.toml | 7 +-- src/ext/async_std.rs | 11 ++++ src/ext/mod.rs | 6 +++ src/ext/tokio1.rs | 88 +++++++++++++++++++++++++++++++ src/ext/triggered.rs | 39 ++++++++++++++ src/impl_arbitrary.rs | 18 ------- src/impl_async_std.rs | 25 --------- src/impl_tokio1.rs | 45 ---------------- src/impl_triggered.rs | 8 --- src/lib.rs | 117 +++++++++++++++++++++++++++++++----------- src/macros.rs | 67 ++++++++++++++++++++++++ 11 files changed, 300 insertions(+), 131 deletions(-) create mode 100644 src/ext/async_std.rs create mode 100644 src/ext/mod.rs create mode 100644 src/ext/tokio1.rs create mode 100644 src/ext/triggered.rs delete mode 100644 src/impl_arbitrary.rs delete mode 100644 src/impl_async_std.rs delete mode 100644 src/impl_tokio1.rs delete mode 100644 src/impl_triggered.rs create mode 100644 src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index 5e3da1b..2b9296d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,11 @@ [package] name = "drop-guard" -version = "0.1.1" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" [features] -# keeping this in default to maintain semver compatibility -default = [ "arbitrary" ] - -arbitrary = [ ] +default = [ ] std = [ ] diff --git a/src/ext/async_std.rs b/src/ext/async_std.rs new file mode 100644 index 0000000..a2dae76 --- /dev/null +++ b/src/ext/async_std.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "async-std-task")] +pub use join_handle_ext::*; + +#[cfg(feature = "async-std-task")] +mod join_handle_ext { + use async_std::task::JoinHandle; + + use crate::DropFn; + + crate::macros::join_handle_ext!(JoinHandleExt for JoinHandle, AbortOnDrop, cancel, |T| T); +} diff --git a/src/ext/mod.rs b/src/ext/mod.rs new file mode 100644 index 0000000..eecbb0a --- /dev/null +++ b/src/ext/mod.rs @@ -0,0 +1,6 @@ +pub mod async_std; + +pub mod tokio1; + +#[cfg(feature = "triggered")] +pub mod triggered; diff --git a/src/ext/tokio1.rs b/src/ext/tokio1.rs new file mode 100644 index 0000000..8aa704b --- /dev/null +++ b/src/ext/tokio1.rs @@ -0,0 +1,88 @@ +#[cfg(feature = "tokio1-task")] +pub use join_handle_ext::*; + +#[cfg(feature = "tokio1-sync")] +pub use sender_ext::*; + +#[cfg(all(feature = "tokio1-sync", feature = "std"))] +pub use semaphore_ext::*; + +#[cfg(feature = "tokio1-task")] +mod join_handle_ext { + use tokio1::task::{JoinError, JoinHandle}; + + use crate::DropFn; + + crate::macros::join_handle_ext!(JoinHandleExt for JoinHandle, AbortOnDrop, abort, |T| Result); +} + +#[cfg(feature = "tokio1-sync")] +mod sender_ext { + use tokio1::sync::oneshot::Sender; + + use crate::{DropFn, DropGuard}; + + pub struct SendOnDrop(T); + impl DropFn for SendOnDrop { + type Data = Sender; + + fn on_drop(self, data: Self::Data) { + _ = data.send(self.0); + } + } + + trait Sealed {} + pub trait SenderExt { + type Value; + + /// Wraps the sender in a [`DropGuard`] that will [`send`](Sender::send) the given `value` on drop. + fn send_on_drop(self, value: Self::Value) -> DropGuard>; + } + + impl Sealed for Sender {} + impl SenderExt for Sender { + type Value = T; + + fn send_on_drop(self, value: T) -> DropGuard> { + DropGuard::with_data(SendOnDrop(value), self) + } + } +} + +#[cfg(all(feature = "tokio1-sync", feature = "std"))] +mod semaphore_ext { + use std::marker::PhantomData; + use std::ops::Deref; + + use tokio1::sync::Semaphore; + + use crate::{DropFn, DropGuard}; + + pub struct CloseOnDrop(PhantomData T>); + impl DropFn for CloseOnDrop + where + T: Deref, + { + type Data = T; + + fn on_drop(self, data: Self::Data) { + data.close(); + } + } + + trait Sealed {} + pub trait SemaphoreExt: Sized + Deref { + /// Wraps the semaphore in a [`DropGuard`] that will call [`Semaphore::close`] on drop. + fn close_on_drop(self) -> DropGuard>; + } + + impl Sealed for T where T: Deref {} + impl SemaphoreExt for T + where + T: Deref, + { + fn close_on_drop(self) -> DropGuard> { + DropGuard::with_data(CloseOnDrop(PhantomData), self) + } + } +} diff --git a/src/ext/triggered.rs b/src/ext/triggered.rs new file mode 100644 index 0000000..2945326 --- /dev/null +++ b/src/ext/triggered.rs @@ -0,0 +1,39 @@ +use triggered::Trigger; + +use crate::DropFn; + +pub struct TriggerOnDrop; +impl DropFn for TriggerOnDrop { + type Data = Trigger; + + fn on_drop(self, data: Self::Data) { + data.trigger(); + } +} + +crate::macros::ext_trait!(TriggerExt: Sealed for Trigger { + /// Wraps the trigger in a [`DropGuard`](crate::DropGuard) that will call [`Trigger::trigger`] on drop. + fn trigger_on_drop -> TriggerOnDrop; +}); + +#[cfg(test)] +mod tests { + use triggered::trigger; + + use crate::ext::triggered::TriggerExt; + + #[test] + fn test() { + let (tx, rx) = trigger(); + + assert!(!rx.is_triggered()); + + { + let _guard = tx.trigger_on_drop(); + + assert!(!rx.is_triggered()); + } + + assert!(rx.is_triggered()); + } +} diff --git a/src/impl_arbitrary.rs b/src/impl_arbitrary.rs deleted file mode 100644 index e736d17..0000000 --- a/src/impl_arbitrary.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::DropGuarded; - -pub struct ArbitraryDropGuard ()>(Option); - -impl ()> ArbitraryDropGuard { - #[inline] - pub const fn new(f: F) -> Self { - Self(Some(f)) - } -} - -impl ()> DropGuarded for ArbitraryDropGuard { - fn cancel(mut self) { - if let Some(f) = self.0.take() { - f(); - } - } -} diff --git a/src/impl_async_std.rs b/src/impl_async_std.rs deleted file mode 100644 index 3774983..0000000 --- a/src/impl_async_std.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[cfg(feature = "async-std-task")] -mod impl_task { - use async_std::task::JoinHandle; - - use crate::{DropGuard, DropGuarded}; - - impl DropGuarded for JoinHandle { - #[inline] - fn cancel(self) { - let _ = JoinHandle::::cancel(self); - } - } - - impl core::future::Future for DropGuard> { - type Output = T; - - fn poll( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - let handle = (*self).inner.as_mut().expect("can only be None in drop"); - core::pin::Pin::new(handle).poll(cx) - } - } -} diff --git a/src/impl_tokio1.rs b/src/impl_tokio1.rs deleted file mode 100644 index d3c2d76..0000000 --- a/src/impl_tokio1.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[cfg(feature = "tokio1-task")] -mod impl_task { - use crate::{DropGuard, DropGuarded}; - - use tokio1::task::{JoinError, JoinHandle}; - - impl DropGuarded for JoinHandle { - #[inline] - fn cancel(self) { - self.abort(); - } - } - - #[cfg(feature = "tokio1-task")] - impl core::future::Future for DropGuard> { - type Output = Result; - - fn poll( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - let handle = (*self).inner.as_mut().expect("can only be None in drop"); - core::pin::Pin::new(handle).poll(cx) - } - } -} - -#[cfg(feature = "tokio1-sync")] -mod impl_sync { - use crate::DropGuarded; - - impl DropGuarded for tokio1::sync::oneshot::Sender<()> { - #[inline] - fn cancel(self) { - let _ = self.send(()); - } - } - #[cfg(feature = "std")] - impl DropGuarded for std::sync::Arc { - #[inline] - fn cancel(self) { - self.close(); - } - } -} diff --git a/src/impl_triggered.rs b/src/impl_triggered.rs deleted file mode 100644 index 41d6b93..0000000 --- a/src/impl_triggered.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::DropGuarded; - -impl DropGuarded for triggered::Trigger { - #[inline] - fn cancel(self) { - self.trigger(); - } -} diff --git a/src/lib.rs b/src/lib.rs index 2eae83f..be5cab1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,46 +1,103 @@ -pub struct DropGuard { - inner: Option, +//! Generic RAII wrappers that execute actions when the guard is dropped in the style of Zig's +//! [`defer`](https://ziglang.org/documentation/master/#defer) statement. +//! +//! Take this rather contrived example: +//! ``` +//! use std::cell::RefCell; +//! use drop_guard::defer; +//! +//! let s = RefCell::new(String::new()); +//! { +//! let _guard = defer(|| s.borrow_mut().push_str("World")); +//! let _guard = defer(|| s.borrow_mut().push_str(", ")); +//! s.borrow_mut().push_str("Hello"); +//! } +//! +//! assert_eq!(s.into_inner(), "Hello, World"); +//! ``` + +pub(crate) mod macros; + +pub mod ext; + +use std::mem::ManuallyDrop; +use std::ops::{Deref, DerefMut}; + +/// Defers execution of the provided [`DropFn`] until the returned [`DropGuard`] is dropped. +pub const fn defer>(f: F) -> DropGuard { + DropGuard::new(f) } -impl DropGuard { - #[inline] - pub fn unguard(mut self) -> T { - self.inner.take().expect("only None in drop") +/// Defers execution of the provided [`DropFn`], with data `d`, until the returned [`DropGuard`] is dropped. +pub const fn defer_with_data(f: F, d: F::Data) -> DropGuard { + DropGuard::with_data(f, d) +} + +pub trait DropFn { + // TODO: Use a default of `()` once the associated_type_defaults (rust-lang/rust#29661) feature lands + type Data; + + fn on_drop(self, data: Self::Data); +} + +impl ()> DropFn for F { + type Data = (); + + fn on_drop(self, _: ()) { + self() } } -pub trait DropGuarded { - fn cancel(self); -} +pub struct DropGuard(ManuallyDrop, ManuallyDrop); -impl DropGuard { - #[inline] - pub const fn new(guarded: T) -> Self { - Self { - inner: Some(guarded), +impl DropGuard { + /// Same as [`defer_with_data`]. + pub const fn with_data(f: F, d: F::Data) -> Self { + Self(ManuallyDrop::new(f), ManuallyDrop::new(d)) + } + + pub fn unguard(slot: Self) -> F::Data { + Self::into_inner(slot).1 + } + + pub fn into_inner(slot: Self) -> (F, F::Data) { + let mut slot = ManuallyDrop::new(slot); + // SAFETY: the DropGuard (slot) is wrapped in a ManuallyDrop, so its Drop impl will not be run + unsafe { + ( + ManuallyDrop::take(&mut slot.0), + ManuallyDrop::take(&mut slot.1), + ) } } } -impl Drop for DropGuard { - #[inline] +impl> DropGuard { + /// Same as [`defer`]. + pub const fn new(f: F) -> Self { + Self::with_data(f, ()) + } +} + +impl Drop for DropGuard { fn drop(&mut self) { - if let Some(inner) = self.inner.take() { - inner.cancel(); - } + // SAFETY: see guarantees on the Drop trait + let f = unsafe { ManuallyDrop::take(&mut self.0) }; + let d = unsafe { ManuallyDrop::take(&mut self.1) }; + f.on_drop(d); } } -#[cfg(feature = "arbitrary")] -mod impl_arbitrary; -#[cfg(feature = "arbitrary")] -pub use impl_arbitrary::ArbitraryDropGuard; +impl Deref for DropGuard { + type Target = F::Data; -#[cfg(feature = "async-std")] -mod impl_async_std; + fn deref(&self) -> &Self::Target { + &self.1 + } +} -#[cfg(feature = "tokio1")] -mod impl_tokio1; - -#[cfg(feature = "triggered")] -mod impl_triggered; +impl DerefMut for DropGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.1 + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..f08226d --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,67 @@ +macro_rules! ext_trait { + ($name:ident: $sealed:ident for $t:ty {$( + $(#[$method_meta:meta])* + fn $method:ident -> $impl:ident; + )+}) => { + trait $sealed {} + #[allow(private_bounds)] + pub trait $name: $sealed {$( + $(#[$method_meta])* + fn $method(self) -> $crate::DropGuard<$impl>; + )+} + + impl $sealed for $t {} + impl $name for $t {$( + fn $method(self) -> $crate::DropGuard<$impl> { + $crate::DropGuard::with_data($impl, self) + } + )+} + }; +} +pub(crate) use ext_trait; + +macro_rules! join_handle_ext { + ($name:ident for $t:ident, $impl:ident, $call_method:ident, |$result_bind:ident| $result:ty) => { + use std::marker::PhantomData; + + use crate::DropGuard; + + pub struct $impl(PhantomData T>); + impl DropFn for $impl { + type Data = $t; + + fn on_drop(self, data: Self::Data) { + _ = data.$call_method(); + } + } + + trait Sealed {} + pub trait $name { + type Result; + + #[doc = concat!("Wraps the join handle in a [`DropGuard`] that will call [`", stringify!($t), "::", stringify!($call_method), "`] on drop.")] + fn abort_on_drop(self) -> DropGuard<$impl>; + } + + impl Sealed for $t {} + impl $name for $t { + type Result = T; + + fn abort_on_drop(self) -> DropGuard<$impl> { + DropGuard::with_data($impl(PhantomData), self) + } + } + + impl<$result_bind> core::future::Future for DropGuard<$impl<$result_bind>> { + type Output = $result; + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + core::pin::Pin::new(&mut **self).poll(cx) + } + } + }; +} +pub(crate) use join_handle_ext;