Skip to content

Commit 8ed1e31

Browse files
authored
Rollup merge of rust-lang#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](rust-lang#143612 (comment)) 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!
2 parents bfe154b + aabe3f3 commit 8ed1e31

File tree

5 files changed

+207
-0
lines changed

5 files changed

+207
-0
lines changed

core/src/mem/drop_guard.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use crate::fmt::{self, Debug};
2+
use crate::mem::ManuallyDrop;
3+
use crate::ops::{Deref, DerefMut};
4+
5+
/// Wrap a value and run a closure when dropped.
6+
///
7+
/// This is useful for quickly creating desructors inline.
8+
///
9+
/// # Examples
10+
///
11+
/// ```rust
12+
/// # #![allow(unused)]
13+
/// #![feature(drop_guard)]
14+
///
15+
/// use std::mem::DropGuard;
16+
///
17+
/// {
18+
/// // Create a new guard around a string that will
19+
/// // print its value when dropped.
20+
/// let s = String::from("Chashu likes tuna");
21+
/// let mut s = DropGuard::new(s, |s| println!("{s}"));
22+
///
23+
/// // Modify the string contained in the guard.
24+
/// s.push_str("!!!");
25+
///
26+
/// // The guard will be dropped here, printing:
27+
/// // "Chashu likes tuna!!!"
28+
/// }
29+
/// ```
30+
#[unstable(feature = "drop_guard", issue = "144426")]
31+
#[doc(alias = "ScopeGuard")]
32+
#[doc(alias = "defer")]
33+
pub struct DropGuard<T, F>
34+
where
35+
F: FnOnce(T),
36+
{
37+
inner: ManuallyDrop<T>,
38+
f: ManuallyDrop<F>,
39+
}
40+
41+
impl<T, F> DropGuard<T, F>
42+
where
43+
F: FnOnce(T),
44+
{
45+
/// Create a new instance of `DropGuard`.
46+
///
47+
/// # Example
48+
///
49+
/// ```rust
50+
/// # #![allow(unused)]
51+
/// #![feature(drop_guard)]
52+
///
53+
/// use std::mem::DropGuard;
54+
///
55+
/// let value = String::from("Chashu likes tuna");
56+
/// let guard = DropGuard::new(value, |s| println!("{s}"));
57+
/// ```
58+
#[unstable(feature = "drop_guard", issue = "144426")]
59+
#[must_use]
60+
pub const fn new(inner: T, f: F) -> Self {
61+
Self { inner: ManuallyDrop::new(inner), f: ManuallyDrop::new(f) }
62+
}
63+
64+
/// Consumes the `DropGuard`, returning the wrapped value.
65+
///
66+
/// This will not execute the closure. This is implemented as an associated
67+
/// function to prevent any potential conflicts with any other methods called
68+
/// `into_inner` from the `Deref` and `DerefMut` impls.
69+
///
70+
/// It is typically preferred to call this function instead of `mem::forget`
71+
/// because it will return the stored value and drop variables captured
72+
/// by the closure instead of leaking their owned resources.
73+
///
74+
/// # Example
75+
///
76+
/// ```rust
77+
/// # #![allow(unused)]
78+
/// #![feature(drop_guard)]
79+
///
80+
/// use std::mem::DropGuard;
81+
///
82+
/// let value = String::from("Nori likes chicken");
83+
/// let guard = DropGuard::new(value, |s| println!("{s}"));
84+
/// assert_eq!(DropGuard::into_inner(guard), "Nori likes chicken");
85+
/// ```
86+
#[unstable(feature = "drop_guard", issue = "144426")]
87+
#[inline]
88+
pub fn into_inner(guard: Self) -> T {
89+
// First we ensure that dropping the guard will not trigger
90+
// its destructor
91+
let mut guard = ManuallyDrop::new(guard);
92+
93+
// Next we manually read the stored value from the guard.
94+
//
95+
// SAFETY: this is safe because we've taken ownership of the guard.
96+
let value = unsafe { ManuallyDrop::take(&mut guard.inner) };
97+
98+
// Finally we drop the stored closure. We do this *after* having read
99+
// the value, so that even if the closure's `drop` function panics,
100+
// unwinding still tries to drop the value.
101+
//
102+
// SAFETY: this is safe because we've taken ownership of the guard.
103+
unsafe { ManuallyDrop::drop(&mut guard.f) };
104+
value
105+
}
106+
}
107+
108+
#[unstable(feature = "drop_guard", issue = "144426")]
109+
impl<T, F> Deref for DropGuard<T, F>
110+
where
111+
F: FnOnce(T),
112+
{
113+
type Target = T;
114+
115+
fn deref(&self) -> &T {
116+
&*self.inner
117+
}
118+
}
119+
120+
#[unstable(feature = "drop_guard", issue = "144426")]
121+
impl<T, F> DerefMut for DropGuard<T, F>
122+
where
123+
F: FnOnce(T),
124+
{
125+
fn deref_mut(&mut self) -> &mut T {
126+
&mut *self.inner
127+
}
128+
}
129+
130+
#[unstable(feature = "drop_guard", issue = "144426")]
131+
impl<T, F> Drop for DropGuard<T, F>
132+
where
133+
F: FnOnce(T),
134+
{
135+
fn drop(&mut self) {
136+
// SAFETY: `DropGuard` is in the process of being dropped.
137+
let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
138+
139+
// SAFETY: `DropGuard` is in the process of being dropped.
140+
let f = unsafe { ManuallyDrop::take(&mut self.f) };
141+
142+
f(inner);
143+
}
144+
}
145+
146+
#[unstable(feature = "drop_guard", issue = "144426")]
147+
impl<T, F> Debug for DropGuard<T, F>
148+
where
149+
T: Debug,
150+
F: FnOnce(T),
151+
{
152+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153+
fmt::Debug::fmt(&**self, f)
154+
}
155+
}

core/src/mem/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ mod transmutability;
2121
#[unstable(feature = "transmutability", issue = "99571")]
2222
pub use transmutability::{Assume, TransmuteFrom};
2323

24+
mod drop_guard;
25+
#[unstable(feature = "drop_guard", issue = "144426")]
26+
pub use drop_guard::DropGuard;
27+
2428
// This one has to be a re-export (rather than wrapping the underlying intrinsic) so that we can do
2529
// the special magic "types have equal size" check at the call site.
2630
#[stable(feature = "rust1", since = "1.0.0")]

coretests/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#![feature(core_private_diy_float)]
3030
#![feature(cstr_display)]
3131
#![feature(dec2flt)]
32+
#![feature(drop_guard)]
3233
#![feature(duration_constants)]
3334
#![feature(duration_constructors)]
3435
#![feature(duration_constructors_lite)]

coretests/tests/mem.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use core::mem::*;
22
use core::{array, ptr};
3+
use std::cell::Cell;
34
#[cfg(panic = "unwind")]
45
use std::rc::Rc;
56

@@ -795,3 +796,48 @@ fn const_maybe_uninit_zeroed() {
795796

796797
assert_eq!(unsafe { (*UNINIT.0.cast::<[[u8; SIZE]; 1]>())[0] }, [0u8; SIZE]);
797798
}
799+
800+
#[test]
801+
fn drop_guards_only_dropped_by_closure_when_run() {
802+
let value_drops = Cell::new(0);
803+
let value = DropGuard::new((), |()| value_drops.set(1 + value_drops.get()));
804+
let closure_drops = Cell::new(0);
805+
let guard = DropGuard::new(value, |_| closure_drops.set(1 + closure_drops.get()));
806+
assert_eq!(value_drops.get(), 0);
807+
assert_eq!(closure_drops.get(), 0);
808+
drop(guard);
809+
assert_eq!(value_drops.get(), 1);
810+
assert_eq!(closure_drops.get(), 1);
811+
}
812+
813+
#[test]
814+
fn drop_guard_into_inner() {
815+
let dropped = Cell::new(false);
816+
let value = DropGuard::new(42, |_| dropped.set(true));
817+
let guard = DropGuard::new(value, |_| dropped.set(true));
818+
let inner = DropGuard::into_inner(guard);
819+
assert_eq!(dropped.get(), false);
820+
assert_eq!(*inner, 42);
821+
}
822+
823+
#[test]
824+
#[cfg(panic = "unwind")]
825+
fn drop_guard_always_drops_value_if_closure_drop_unwinds() {
826+
// Create a value with a destructor, which we will validate ran successfully.
827+
let mut value_was_dropped = false;
828+
let value_with_tracked_destruction = DropGuard::new((), |_| value_was_dropped = true);
829+
830+
// Create a closure that will begin unwinding when dropped.
831+
let drop_bomb = DropGuard::new((), |_| panic!());
832+
let closure_that_panics_on_drop = move |_| {
833+
let _drop_bomb = drop_bomb;
834+
};
835+
836+
// This will run the closure, which will panic when dropped. This should
837+
// run the destructor of the value we passed, which we validate.
838+
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
839+
let guard = DropGuard::new(value_with_tracked_destruction, closure_that_panics_on_drop);
840+
DropGuard::into_inner(guard);
841+
}));
842+
assert!(value_was_dropped);
843+
}

std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@
330330
#![feature(clone_to_uninit)]
331331
#![feature(core_intrinsics)]
332332
#![feature(core_io_borrowed_buf)]
333+
#![feature(drop_guard)]
333334
#![feature(duration_constants)]
334335
#![feature(error_generic_member_access)]
335336
#![feature(error_iter)]

0 commit comments

Comments
 (0)