Skip to content

Add no-value DropGuard::new(|| ...) - #161550

Draft
dtolnay wants to merge 1 commit into
rust-lang:mainfrom
dtolnay:dropguard
Draft

Add no-value DropGuard::new(|| ...)#161550
dtolnay wants to merge 1 commit into
rust-lang:mainfrom
dtolnay:dropguard

Conversation

@dtolnay

@dtolnay dtolnay commented Aug 22, 2026

Copy link
Copy Markdown
Member

Tracking issue: #144426

According to #144426 (comment), in some projects DropGuard<(), ...> is the common case.

@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 Aug 22, 2026
@rustbot

rustbot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

r? @clarfonthey

rustbot has assigned @clarfonthey.
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

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: libs
  • libs expanded to 12 candidates
  • Random selection from JohnTitor, Mark-Simulacrum, clarfonthey, nia-e

@dtolnay
dtolnay marked this pull request as draft August 22, 2026 17:18
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 22, 2026
/// Not publicly nameable outside libcore and not on track for stabilization.
#[unstable(feature = "drop_guard_unit_fn", issue = "none")]
#[allow(missing_debug_implementations)]
pub enum UnitFn {}

@clarfonthey clarfonthey Aug 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how I feel about this hack.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about having a UnitFn<F> wrapper? Then that wrapper would have this "feed ()" logic attached to it (whilst remaining non-constructible and non-path-nameable from downstream), and the impl would become:

impl<(), F: FnOnce()> DropGuard<(), UnitFn<F>> {
    /// Convenience for <code>[Self::with]\((), |()| f()\)</code>.
    pub fn new(f: F) -> Self {
        Self::with((), UnitFn(f))
    }
}

Feels more honest w.r.t. all that happens, here

/// // print its value when dropped.
/// let s = String::from("Chashu likes tuna");
/// let mut s = DropGuard::new(s, |s| println!("{s}"));
/// let mut s = DropGuard::with(s, |s| println!("{s}"));

@yoshuawuyts yoshuawuyts Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to call this something other than with? Separately from this, I'm working to to propose making with a reserved keyword for an effects notation in the next edition.

That of course has not been accepted, and the lang team hasn't given any indication they'd like this to happen. But still: it feels pretty bad if the first API added to the stdlib called "with" in 11 years is one I ended up helping introduce, only to advocate we rename it a couple of months later 😅

View changes since the review

@yoshuawuyts yoshuawuyts Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, looking at existing uses of with in the stdlib all seem to be methods on the instance, not used for constructors. The exception seems to be with_hasher, but that feels different from a bare with method?

To throw something out there: perhaps something like with_value could work here instead?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DropGuard::new_empty?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, looking at existing uses of with in the stdlib all seem to be methods on the instance, not used for constructors. The exception seems to be with_hasher, but that feels different from a bare with method?

There's also a large number of with_capacity (and some with_capacity_and_hasher) ctors.

(this is not to say that I'm particularly fond of plain with)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also the possibility, as this is a mere convenience constructor, to have it be a From impl?

let a = DropGuard::new(state, |state| {}));

let b = DropGuard::from(|| {});

This also hints a bit more about there being some "adjustment" happening when doing this construction/conversion.


Another option which was mentioned somewhere was DropGuard::from_fn(|| { … }).


FWIW, either way I wouldn't recommend using new() for the convenience, narrower, case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielhenrymantilla I honestly quite like DropGuard::from/DropGuard::from_fn for this case. That's a great suggestion, thank you!

Zalathar added a commit to Zalathar/rust that referenced this pull request Aug 25, 2026
…they

Use `drop_guard` in some places in {core,alloc,std}

- Tracking issue: rust-lang#144426
- Will conflict with rust-lang#161520
- rust-lang#161550 would also be cool occasionally

Didn't touch the places where manual `impl Drop`s had `#[inline]` on their `fn drop` or where the guard type had other `impl`s beside `Drop` and/or was named a lot.

No LLMs used, only pure human slop.
@yoshuawuyts

Copy link
Copy Markdown
Member

Looking at #161702 which migrates a number of existing drop guards in the Rust codebase to core::mem::DropGuard, we can observe a total of 21 migrations. All 21 of those have a new function capturing a value and using it as part of the guard. Though the author does mention that having a no-value constructor might be: "cool occasionally".

In #161550 (comment) @danielhenrymantilla suggests using DropGuard::from or alternatively DropGuard::from_fn as the name for this API instead, stating that: "[...] I wouldn't recommend using new() for the convenience, narrower, case." I believe that #161702 substantiates that a no-value constructor is indeed a: "convenience, narrower case", and the new method should be the one to take a value.

@GrigorenkoPV

GrigorenkoPV commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

All 21 of those have a new function capturing a value and using it as part of the guard. Though the author does mention that having a no-value constructor might be: "cool occasionally".

At least in some places I could write

let a = expr1;
let b = expr2;
let guard = DropGuard::new((), |()| {
    // code that uses `a` & `b`
});

instead of

let guard = DropGuard::new(
    (expr1, expr2),
    |(a, b)| {
        // code that uses `a` & `b` 
    },
);

but the latter seemed a bit more clear about where the values are used.

My personal dream scenario here would probably be explicit lambda captures and value-less DropGuard constructor. Along the lines of (with C++esque placeholder syntax for lambdas):

let guard = DropGuard::from_fn(
    [a = expr1, b = expr2] {
        // code using `a` & `b` 
    },
);

(Out of all suggested names, I like from_fn the most, it feels familiar and does not look like it implies From::from being involved)


Explicit captures are a dream of mine either way because currently Rust only has what is effectively [&] (capture everything by reference) and [=] (capture everything by value) in C++, and while this is not as bad as it is in C++, where [&] leads to dangling references and [=] leads to implicit copies (clones in Rust terms), there is clearly room for improvement here. See all the .use, move(..), etc. experiments out there.


Another DropGuard pattern I found myself using was

let guard = DropGuard::new((expr1, expr2, expr3), |(a, b, c)| { /* ... */ });
let (a, b, c) = &mut *guard;

Not sure what could be done here to improve the ergonomics.
One could probably define a struct { a: _, b: _, c: _ } to put it inside the DropGuard and both patterns, but at that point you just might as well impl Drop for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. 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.

7 participants