Rewrite the entire thing, add docs
The new implementation has fewer surprises!
This commit is contained in:
parent
ba59933810
commit
13ab54f55d
|
@ -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 = [ ]
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
pub mod async_std;
|
||||
|
||||
pub mod tokio1;
|
||||
|
||||
#[cfg(feature = "triggered")]
|
||||
pub mod triggered;
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
use crate::DropGuarded;
|
||||
|
||||
impl DropGuarded for triggered::Trigger {
|
||||
#[inline]
|
||||
fn cancel(self) {
|
||||
self.trigger();
|
||||
}
|
||||
}
|
117
src/lib.rs
117
src/lib.rs
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue