about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-07-29 16:16:41 +1000
committerGitHub <noreply@github.com>2025-07-29 16:16:41 +1000
commited7d6a941d07c7ec9e9ef1dd7d840c233541fb35 (patch)
treed47501bd8a16844e8883828eaea20d16b5050a69
parent95ce5af4fe4575a558e27fcfebc0646936f39618 (diff)
parent68f08c5dd9f5bee706a221df13660cc9b3a71c93 (diff)
downloadrust-ed7d6a941d07c7ec9e9ef1dd7d840c233541fb35.tar.gz
rust-ed7d6a941d07c7ec9e9ef1dd7d840c233541fb35.zip
Rollup merge of #144236 - yoshuawuyts:drop-guard, r=Mark-Simulacrum
Add `core::mem::DropGuard`

## 1.0 Summary

This PR introduces a new type `core::mem::DropGuard` which wraps a value and runs a closure when the value is dropped.

```rust
use core::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!!!"
```

## 2.0 Motivation

A number of programming languages include constructs like `try..finally` or `defer` to run code as the last piece of a particular sequence, regardless of whether an error occurred. This is typically used to clean up resources, like closing files, freeing memory, or unlocking resources. In Rust we use the `Drop` trait instead, allowing us to [never having to manually close sockets](https://blog.skylight.io/rust-means-never-having-to-close-a-socket/).

While `Drop` (and RAII in general) has been working incredibly well for Rust in general, sometimes it can be a little verbose to setup. In particular when upholding invariants are local to functions, having a quick inline way to setup an `impl Drop` can be incredibly convenient. We can see this in use in the Rust stdlib, which has a number of private `DropGuard` impls used internally:

- [library/alloc/src/vec/drain.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/vec/drain.rs#L177)
- [library/alloc/src/boxed/thin.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/boxed/thin.rs#L362)
- [library/alloc/src/slice.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/slice.rs#L413)
- [library/alloc/src/collections/linked_list.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/collections/linked_list.rs#L1135)
- [library/alloc/src/collections/binary_heap/mod.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/collections/binary_heap/mod.rs#L1816)
- [library/alloc/src/collections/btree/map.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/collections/btree/map.rs#L1715)
- [library/alloc/src/collections/vec_deque/drain.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/collections/vec_deque/drain.rs#L95)
- [library/alloc/src/vec/into_iter.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/alloc/src/vec/into_iter.rs#L488)
- [library/std/src/os/windows/process.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/library/std/src/os/windows/process.rs#L320)
- [tests/ui/process/win-proc-thread-attributes.rs](https://github.com/rust-lang/rust/blob/9982d6462bedf1e793f7b2dbd655a4e57cdf67d4/tests/ui/process/win-proc-thread-attributes.rs#L17)

## 3.0 Design

This PR implements what can be considered about the simplest possible design:

1. A single type `DropGuard` which takes both a generic type `T` and a closure `F`.
2. `Deref` + `DerefMut` impls to make it easy to work with the `T` in the guard.
3. An `impl Drop` on the guard which calls the closure `F` on drop.
4. An inherent `fn into_inner` which takes the type `T` out of the guard without calling the closure `F`.

Notably this design does not allow divergent behavior based on the type of drop that has occurred. The [`scopeguard` crate](https://docs.rs/scopeguard/latest/scopeguard/index.html) includes additional `on_success` and `on_onwind` variants which can be used to branch on unwind behavior instead. However [in a lot of cases](https://github.com/rust-lang/rust/issues/143612#issuecomment-3053928328) this doesn’t seem necessary, and using the arm/disarm pattern seems to provide much the same functionality:

```rust
let guard = DropGuard::new((), |s| ...);  // 1. Arm the guard
other_function();                         // 2. Perform operations
guard.into_inner();                       // 3. Disarm the guard
```

`DropGuard` combined with this pattern seems like it should cover the vast majority of use cases for quick, inline destructors. It certainly seems like it should cover all existing uses in the stdlib, as well as all existing uses in crates like [hashbrown](https://github.com/search?q=repo%3Arust-lang%2Fhashbrown%20guard&type=code).

## 4.0 Acknowledgements

This implementation is based on the [mini-scopeguard crate](https://github.com/yoshuawuyts/mini-scopeguard) which in turn is based on the [scopeguard  crate](https://docs.rs/scopeguard). The implementations only differ superficially; because of the nature of the problem there is only really one obvious way to structure the solution. And the scopeguard crate got that right!

## 5.0 Conclusion

This PR adds a new type `core::mem::DropGuard` to the stdlib which adds a small convenience helper to create inline destructors with. This would bring the majority of the functionality of the `scopeguard` crate into the stdlib, which is the [49th most downloaded crate](https://crates.io/crates?sort=downloads) on crates.io (387 million downloads).

Given the actual implementation of `DropGuard` is only around 60 lines, it seems to hit that sweet spot of low-complexity / high-impact that makes for a particularly efficient stdlib addition. Which is why I’m putting this forward for consideration; thanks!
-rw-r--r--library/core/src/mem/drop_guard.rs155
-rw-r--r--library/core/src/mem/mod.rs4
-rw-r--r--library/coretests/tests/lib.rs1
-rw-r--r--library/coretests/tests/mem.rs46
-rw-r--r--library/std/src/lib.rs1
5 files changed, 207 insertions, 0 deletions
diff --git a/library/core/src/mem/drop_guard.rs b/library/core/src/mem/drop_guard.rs
new file mode 100644
index 00000000000..47ccb69acc8
--- /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<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)
+    }
+}
diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs
index 33407637ab3..db4c8e9e551 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 57a89365fa9..029a7b00ad3 100644
--- a/library/coretests/tests/lib.rs
+++ b/library/coretests/tests/lib.rs
@@ -29,6 +29,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 9c15be4a8c4..e896c61ef48 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 433e013b40f..77301d7228e 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -330,6 +330,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)]