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
Open

Conversation

yoshuawuyts
Copy link
Member

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.

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.

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:

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 includes additional on_success and on_onwind variants which can be used to branch on unwind behavior instead. However in a lot of cases this doesn’t seem necessary, and using the arm/disarm pattern seems to provide much the same functionality:

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.

4.0 Acknowledgements

This implementation is based on the mini-scopeguard crate which in turn is based on the scopeguard crate. 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 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!

@rustbot
Copy link
Collaborator

rustbot commented Jul 21, 2025

r? @Mark-Simulacrum

rustbot has assigned @Mark-Simulacrum.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 21, 2025
@workingjubilee
Copy link
Member

this appears to be revisiting rust-lang/libs-team#622

@rust-log-analyzer

This comment has been minimized.

@yoshuawuyts
Copy link
Member Author

yoshuawuyts commented Jul 21, 2025

this appears to be revisiting rust-lang/libs-team#622

Oh you mean the naming? I guess I missed that issue since it's just a few days old. Most of the names come from the mini-scopeguard crate I put together in preparation for this PR earlier this year. To give some rationale for the names:

  • core::mem::DropGuard groups nicely with core::mem::drop and core::mem::ManuallyDrop. It would be the third drop-related operation we're putting in mem, and it's worth making it sound both similar yet distinctly identifiable.
  • core::mem::DropGuard functions very similarly to all the other guards we have in the stdlib. I think of the "guard" suffix as "does interesting things on drop", which checks out.
  • In the stdlib we've been using the name DropGuard for many years now, with at least 10 instances of it. That seems like pretty good precedent, and a decent indicator that the name is memorable.
  • The ScopeGuard crate uses the ScopeGuard::into_inner method to take a value without running the destructors. Its signature is also nearly identical to all the other into_inner methods we have.

This to me seemed like a reasonable starting point for a contribution. The part I'm least sure about is the into_inner method, since it does something interesting that might be worth calling attention to in a way that into_inner might not. Since this is only an initial impl, I'm not sure we should block on that though. Instead I think we should probably keep that as an open question?

@rust-log-analyzer

This comment has been minimized.

@jieyouxu jieyouxu added the S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. label Jul 21, 2025
@rust-log-analyzer

This comment has been minimized.

@orlp
Copy link
Contributor

orlp commented Jul 21, 2025

Some things missing in this PR from my ACP (which makes sense since it was apparently done in parallel):

  1. The function type in my ACP is defaulted to = fn(T):
pub struct WithDrop<T, F: FnOnce(T) = fn(T)> {

This is similar to how it is defaulted in LazyLock to make writing static declarations easier.

  1. new is not marked const. There's no reason it shouldn't be.
  2. new is not marked #[must_use]. This is important to prevent accidental instant-dropping.
  3. Clone is not derived. I think this somewhat makes sense if you think of this type as exclusively a guard, but I think of this more generally, and under that umbrella there is no reason Clone shouldn't be derived.

There are also some differences:

  1. The name of the type, I proposed WithDrop, but this is best discussed in the ACP, and I placed a comment there.
  2. Debug is not implemented, but rather derived. This gives rather ugly results.
  3. I intentionally did not implement PartialEq, Eq, PartialOrd, Ord, Hash, but this PR does (through derives). It's not clear to me that these traits should be implemented for this type as it's unclear whether these relationships include the on_drop function to compare or not. One user might expect equality to only include the payload, and another might expect it to include the function. As it's written right now it's also dangerous because Deref coercion might change equality results (since post-Deref only the value is compared, but pre-Deref both the value and function).

@yoshuawuyts
Copy link
Member Author

Thanks @orlp, @purplesyringa, @bjorn3, @lukaslueg - you've raised some really good points. I'll try and address your comments either later today or tomorrow. But I wanted to take a moment to say thank you before then ^^

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

yoshuawuyts and others added 2 commits July 24, 2025 15:20
Co-authored-by: Ruby Lazuli <general@patchmixolydic.com>
@rust-log-analyzer

This comment has been minimized.

And start tracking based on the tracking issue.
@yoshuawuyts
Copy link
Member Author

Filed a tracking issue in #144426, updated the #[unstable] attributes, and removed the = fn() -> T default parameter from the type. I never added the Clone impl to begin with, so I believe with that this PR should be in compliance with the T-Libs-API acceptance criteria defined in rust-lang/libs-team#622.

Once the latest CI run is green, I think this should be good to merge!

@rust-log-analyzer

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.