Skip to content

Add core::mem::DropGuard #144236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
155 changes: 155 additions & 0 deletions library/core/src/mem/drop_guard.rs
Original file line number Diff line number Diff line change
@@ -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<T, F>
where
F: FnOnce(T),
{
inner: ManuallyDrop<T>,
f: ManuallyDrop<F>,
}

impl<T, F> DropGuard<T, F>
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<T, F> Deref for DropGuard<T, F>
where
F: FnOnce(T),
{
type Target = T;

fn deref(&self) -> &T {
&*self.inner
}
}

#[unstable(feature = "drop_guard", issue = "144426")]
impl<T, F> DerefMut for DropGuard<T, F>
where
F: FnOnce(T),
{
fn deref_mut(&mut self) -> &mut T {
&mut *self.inner
}
}

#[unstable(feature = "drop_guard", issue = "144426")]
impl<T, F> Drop for DropGuard<T, F>
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<T, F> Debug for DropGuard<T, F>
where
T: Debug,
F: FnOnce(T),
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
4 changes: 4 additions & 0 deletions library/core/src/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
1 change: 1 addition & 0 deletions library/coretests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
46 changes: 46 additions & 0 deletions library/coretests/tests/mem.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::mem::*;
use core::{array, ptr};
use std::cell::Cell;
#[cfg(panic = "unwind")]
use std::rc::Rc;

Expand Down Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Loading