drop-guard.rs/src/lib.rs

142 lines
4.0 KiB
Rust

//! 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");
//! ```
//!
//! Of course, it sucks to need to resort to runtime borrow checking. Thankfully, the example can be rewritten like so:
//! ```
//! use drop_guard::defer_with_data_by;
//!
//! let mut s = String::new();
//! {
//! let mut s = defer_with_data_by(|s| s.push_str("World"), &mut s);
//! let mut s = defer_with_data_by(|s| s.push_str(", "), &mut s);
//! s.push_str("Hello");
//! }
//!
//! assert_eq!(s, "Hello, World");
//! ```
//!
//! But, what's going on here? Well, [`DropGuard`] holds on to two values: a [`DropFn`], and an
//! optional `data`. When we just call `defer`, `data` will be a `()`, but when we call
//! `defer_with_data_by` instead, it will be the second argument. [`DropGuard`] derefences to that data value, enabling you to keep using that
//! data while it's guarded!
pub(crate) mod macros;
pub mod ext;
use std::marker::PhantomData;
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)
}
/// 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)
}
/// Defers execution of the provided [`DropFn`], with data `d`, until the returned [`DropGuard`] is dropped.
pub const fn defer_with_data_by<D, F>(f: F, d: D) -> DropGuard<FnOnceDropFn<F, D>>
where
F: FnOnce(D) -> (),
{
defer_with_data(FnOnceDropFn(f, PhantomData), 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 struct DropGuard<F: DropFn>(ManuallyDrop<F>, ManuallyDrop<F::Data>);
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<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) {
// 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);
}
}
impl<F: DropFn> Deref for DropGuard<F> {
type Target = F::Data;
fn deref(&self) -> &Self::Target {
&self.1
}
}
impl<F: DropFn> DerefMut for DropGuard<F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.1
}
}
pub struct FnOnceDropFn<F: FnOnce(D) -> (), D>(F, PhantomData<fn(D) -> ()>);
impl<F: FnOnce(D) -> (), D> DropFn for FnOnceDropFn<F, D> {
type Data = D;
fn on_drop(self, data: Self::Data) {
self.0(data)
}
}