Rewrite the entire thing, add docs

The new implementation has fewer surprises!
This commit is contained in:
Michael Pfaff 2024-02-03 16:53:44 -05:00
parent ba59933810
commit 13ab54f55d
Signed by: michael
GPG Key ID: CF402C4A012AA9D4
11 changed files with 300 additions and 131 deletions

View File

@ -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 = [ ]

11
src/ext/async_std.rs Normal file
View File

@ -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);
}

6
src/ext/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod async_std;
pub mod tokio1;
#[cfg(feature = "triggered")]
pub mod triggered;

88
src/ext/tokio1.rs Normal file
View File

@ -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<T, JoinError>);
}
#[cfg(feature = "tokio1-sync")]
mod sender_ext {
use tokio1::sync::oneshot::Sender;
use crate::{DropFn, DropGuard};
pub struct SendOnDrop<T>(T);
impl<T> DropFn for SendOnDrop<T> {
type Data = Sender<T>;
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<SendOnDrop<Self::Value>>;
}
impl<T> Sealed for Sender<T> {}
impl<T> SenderExt for Sender<T> {
type Value = T;
fn send_on_drop(self, value: T) -> DropGuard<SendOnDrop<T>> {
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<T>(PhantomData<fn() -> T>);
impl<T> DropFn for CloseOnDrop<T>
where
T: Deref<Target = Semaphore>,
{
type Data = T;
fn on_drop(self, data: Self::Data) {
data.close();
}
}
trait Sealed {}
pub trait SemaphoreExt: Sized + Deref<Target = Semaphore> {
/// Wraps the semaphore in a [`DropGuard`] that will call [`Semaphore::close`] on drop.
fn close_on_drop(self) -> DropGuard<CloseOnDrop<Self>>;
}
impl<T> Sealed for T where T: Deref<Target = Semaphore> {}
impl<T> SemaphoreExt for T
where
T: Deref<Target = Semaphore>,
{
fn close_on_drop(self) -> DropGuard<CloseOnDrop<Self>> {
DropGuard::with_data(CloseOnDrop(PhantomData), self)
}
}
}

39
src/ext/triggered.rs Normal file
View File

@ -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());
}
}

View File

@ -1,18 +0,0 @@
use crate::DropGuarded;
pub struct ArbitraryDropGuard<F: FnOnce() -> ()>(Option<F>);
impl<F: FnOnce() -> ()> ArbitraryDropGuard<F> {
#[inline]
pub const fn new(f: F) -> Self {
Self(Some(f))
}
}
impl<F: FnOnce() -> ()> DropGuarded for ArbitraryDropGuard<F> {
fn cancel(mut self) {
if let Some(f) = self.0.take() {
f();
}
}
}

View File

@ -1,25 +0,0 @@
#[cfg(feature = "async-std-task")]
mod impl_task {
use async_std::task::JoinHandle;
use crate::{DropGuard, DropGuarded};
impl<T> DropGuarded for JoinHandle<T> {
#[inline]
fn cancel(self) {
let _ = JoinHandle::<T>::cancel(self);
}
}
impl<T> core::future::Future for DropGuard<JoinHandle<T>> {
type Output = T;
fn poll(
mut self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let handle = (*self).inner.as_mut().expect("can only be None in drop");
core::pin::Pin::new(handle).poll(cx)
}
}
}

View File

@ -1,45 +0,0 @@
#[cfg(feature = "tokio1-task")]
mod impl_task {
use crate::{DropGuard, DropGuarded};
use tokio1::task::{JoinError, JoinHandle};
impl<T> DropGuarded for JoinHandle<T> {
#[inline]
fn cancel(self) {
self.abort();
}
}
#[cfg(feature = "tokio1-task")]
impl<T> core::future::Future for DropGuard<JoinHandle<T>> {
type Output = Result<T, JoinError>;
fn poll(
mut self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
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<tokio1::sync::Semaphore> {
#[inline]
fn cancel(self) {
self.close();
}
}
}

View File

@ -1,8 +0,0 @@
use crate::DropGuarded;
impl DropGuarded for triggered::Trigger {
#[inline]
fn cancel(self) {
self.trigger();
}
}

View File

@ -1,46 +1,103 @@
pub struct DropGuard<T: DropGuarded> {
inner: Option<T>,
//! 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: DropFn<Data = ()>>(f: F) -> DropGuard<F> {
DropGuard::new(f)
}
impl<T: DropGuarded> DropGuard<T> {
#[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: DropFn>(f: F, d: F::Data) -> DropGuard<F> {
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<F: FnOnce() -> ()> DropFn for F {
type Data = ();
fn on_drop(self, _: ()) {
self()
}
}
pub trait DropGuarded {
fn cancel(self);
}
pub struct DropGuard<F: DropFn>(ManuallyDrop<F>, ManuallyDrop<F::Data>);
impl<T: DropGuarded> DropGuard<T> {
#[inline]
pub const fn new(guarded: T) -> Self {
Self {
inner: Some(guarded),
impl<F: DropFn> DropGuard<F> {
/// 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<T: DropGuarded> Drop for DropGuard<T> {
#[inline]
impl<F: DropFn<Data = ()>> DropGuard<F> {
/// Same as [`defer`].
pub const fn new(f: F) -> Self {
Self::with_data(f, ())
}
}
impl<F: DropFn> Drop for DropGuard<F> {
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<F: DropFn> Deref for DropGuard<F> {
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<F: DropFn> DerefMut for DropGuard<F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.1
}
}

67
src/macros.rs Normal file
View File

@ -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<T>(PhantomData<fn() -> T>);
impl<T> DropFn for $impl<T> {
type Data = $t<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<Self::Result>>;
}
impl<T> Sealed for $t<T> {}
impl<T> $name for $t<T> {
type Result = T;
fn abort_on_drop(self) -> DropGuard<$impl<T>> {
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<Self::Output> {
core::pin::Pin::new(&mut **self).poll(cx)
}
}
};
}
pub(crate) use join_handle_ext;