diff --git a/library/core/src/mem/drop_guard.rs b/library/core/src/mem/drop_guard.rs new file mode 100644 index 0000000000000..47ccb69acc806 --- /dev/null +++ b/library/core/src/mem/drop_guard.rs @@ -0,0 +1,155 @@ +use crate::fmt::{self, Debug}; +use crate::mem::ManuallyDrop; +use crate::ops::{Deref, DerefMut}; + +/// Wrap a value and run a closure when dropped. +/// +/// This is useful for quickly creating desructors inline. +/// +/// # Examples +/// +/// ```rust +/// # #![allow(unused)] +/// #![feature(drop_guard)] +/// +/// use std::mem::DropGuard; +/// +/// { +/// // Create a new guard around a string that will +/// // print its value when dropped. +/// let s = String::from("Chashu likes tuna"); +/// let mut s = DropGuard::new(s, |s| println!("{s}")); +/// +/// // Modify the string contained in the guard. +/// s.push_str("!!!"); +/// +/// // The guard will be dropped here, printing: +/// // "Chashu likes tuna!!!" +/// } +/// ``` +#[unstable(feature = "drop_guard", issue = "144426")] +#[doc(alias = "ScopeGuard")] +#[doc(alias = "defer")] +pub struct DropGuard +where + F: FnOnce(T), +{ + inner: ManuallyDrop, + f: ManuallyDrop, +} + +impl DropGuard +where + F: FnOnce(T), +{ + /// Create a new instance of `DropGuard`. + /// + /// # Example + /// + /// ```rust + /// # #![allow(unused)] + /// #![feature(drop_guard)] + /// + /// use std::mem::DropGuard; + /// + /// let value = String::from("Chashu likes tuna"); + /// let guard = DropGuard::new(value, |s| println!("{s}")); + /// ``` + #[unstable(feature = "drop_guard", issue = "144426")] + #[must_use] + pub const fn new(inner: T, f: F) -> Self { + Self { inner: ManuallyDrop::new(inner), f: ManuallyDrop::new(f) } + } + + /// Consumes the `DropGuard`, returning the wrapped value. + /// + /// This will not execute the closure. This is implemented as an associated + /// function to prevent any potential conflicts with any other methods called + /// `into_inner` from the `Deref` and `DerefMut` impls. + /// + /// It is typically preferred to call this function instead of `mem::forget` + /// because it will return the stored value and drop variables captured + /// by the closure instead of leaking their owned resources. + /// + /// # Example + /// + /// ```rust + /// # #![allow(unused)] + /// #![feature(drop_guard)] + /// + /// use std::mem::DropGuard; + /// + /// let value = String::from("Nori likes chicken"); + /// let guard = DropGuard::new(value, |s| println!("{s}")); + /// assert_eq!(DropGuard::into_inner(guard), "Nori likes chicken"); + /// ``` + #[unstable(feature = "drop_guard", issue = "144426")] + #[inline] + pub fn into_inner(guard: Self) -> T { + // First we ensure that dropping the guard will not trigger + // its destructor + let mut guard = ManuallyDrop::new(guard); + + // Next we manually read the stored value from the guard. + // + // SAFETY: this is safe because we've taken ownership of the guard. + let value = unsafe { ManuallyDrop::take(&mut guard.inner) }; + + // Finally we drop the stored closure. We do this *after* having read + // the value, so that even if the closure's `drop` function panics, + // unwinding still tries to drop the value. + // + // SAFETY: this is safe because we've taken ownership of the guard. + unsafe { ManuallyDrop::drop(&mut guard.f) }; + value + } +} + +#[unstable(feature = "drop_guard", issue = "144426")] +impl Deref for DropGuard +where + F: FnOnce(T), +{ + type Target = T; + + fn deref(&self) -> &T { + &*self.inner + } +} + +#[unstable(feature = "drop_guard", issue = "144426")] +impl DerefMut for DropGuard +where + F: FnOnce(T), +{ + fn deref_mut(&mut self) -> &mut T { + &mut *self.inner + } +} + +#[unstable(feature = "drop_guard", issue = "144426")] +impl Drop for DropGuard +where + F: FnOnce(T), +{ + fn drop(&mut self) { + // SAFETY: `DropGuard` is in the process of being dropped. + let inner = unsafe { ManuallyDrop::take(&mut self.inner) }; + + // SAFETY: `DropGuard` is in the process of being dropped. + let f = unsafe { ManuallyDrop::take(&mut self.f) }; + + f(inner); + } +} + +#[unstable(feature = "drop_guard", issue = "144426")] +impl Debug for DropGuard +where + T: Debug, + F: FnOnce(T), +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 2198d098c4be9..fcb9f25ede2ff 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -21,6 +21,10 @@ mod transmutability; #[unstable(feature = "transmutability", issue = "99571")] pub use transmutability::{Assume, TransmuteFrom}; +mod drop_guard; +#[unstable(feature = "drop_guard", issue = "144426")] +pub use drop_guard::DropGuard; + // This one has to be a re-export (rather than wrapping the underlying intrinsic) so that we can do // the special magic "types have equal size" check at the call site. #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 4cfac9ecc2ab7..c8f516f0a9555 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -30,6 +30,7 @@ #![feature(core_private_diy_float)] #![feature(cstr_display)] #![feature(dec2flt)] +#![feature(drop_guard)] #![feature(duration_constants)] #![feature(duration_constructors)] #![feature(duration_constructors_lite)] diff --git a/library/coretests/tests/mem.rs b/library/coretests/tests/mem.rs index 9c15be4a8c4bd..e896c61ef4881 100644 --- a/library/coretests/tests/mem.rs +++ b/library/coretests/tests/mem.rs @@ -1,5 +1,6 @@ use core::mem::*; use core::{array, ptr}; +use std::cell::Cell; #[cfg(panic = "unwind")] use std::rc::Rc; @@ -795,3 +796,48 @@ fn const_maybe_uninit_zeroed() { assert_eq!(unsafe { (*UNINIT.0.cast::<[[u8; SIZE]; 1]>())[0] }, [0u8; SIZE]); } + +#[test] +fn drop_guards_only_dropped_by_closure_when_run() { + let value_drops = Cell::new(0); + let value = DropGuard::new((), |()| value_drops.set(1 + value_drops.get())); + let closure_drops = Cell::new(0); + let guard = DropGuard::new(value, |_| closure_drops.set(1 + closure_drops.get())); + assert_eq!(value_drops.get(), 0); + assert_eq!(closure_drops.get(), 0); + drop(guard); + assert_eq!(value_drops.get(), 1); + assert_eq!(closure_drops.get(), 1); +} + +#[test] +fn drop_guard_into_inner() { + let dropped = Cell::new(false); + let value = DropGuard::new(42, |_| dropped.set(true)); + let guard = DropGuard::new(value, |_| dropped.set(true)); + let inner = DropGuard::into_inner(guard); + assert_eq!(dropped.get(), false); + assert_eq!(*inner, 42); +} + +#[test] +#[cfg(panic = "unwind")] +fn drop_guard_always_drops_value_if_closure_drop_unwinds() { + // Create a value with a destructor, which we will validate ran successfully. + let mut value_was_dropped = false; + let value_with_tracked_destruction = DropGuard::new((), |_| value_was_dropped = true); + + // Create a closure that will begin unwinding when dropped. + let drop_bomb = DropGuard::new((), |_| panic!()); + let closure_that_panics_on_drop = move |_| { + let _drop_bomb = drop_bomb; + }; + + // This will run the closure, which will panic when dropped. This should + // run the destructor of the value we passed, which we validate. + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let guard = DropGuard::new(value_with_tracked_destruction, closure_that_panics_on_drop); + DropGuard::into_inner(guard); + })); + assert!(value_was_dropped); +} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 323742a75b055..1b1e85cfc2fe7 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -331,6 +331,7 @@ #![feature(clone_to_uninit)] #![feature(core_intrinsics)] #![feature(core_io_borrowed_buf)] +#![feature(drop_guard)] #![feature(duration_constants)] #![feature(error_generic_member_access)] #![feature(error_iter)]